. Finally, the animations (columns 3 and 7)
+// are represented as transformations that take one property value to another when played.
+//
+// ------------------------+---------------------------------------------------------------------------------------+-----------------------------------------------------------------------------------+
+// | +--- Closing an open pane | Opening a closed pane <--+ |
+// AutoSize Mode | | 1. 2. 3. 4. | 5. 6. 7. 8. | |
+// Element | +--> Opened -> Before Closing -> Closing -> After Closing --+-> Closed -> Before Opening -> Opening -> After Opening ---+ |
+// Property | Pane Animation Animation Animation | Pane Animation Animation Animation |
+// ------------------------+---------------------------------------------------------------------------------------+-----------------------------------------------------------------------------------+
+// None: | | |
+// Accordion: | | |
+// height: | 'auto' . . . | 'auto' . . . |
+// overflow: | 'auto' . . 'auto' | 'auto' . . 'auto' |
+// Wrapper: | | |
+// height: | 'auto' . Current -> '0px' . | '0px' . '0px' -> Current 'auto' |
+// opacity: | 1.0 . 1.0 -> 0.0 . | 0.0 . 0.0 -> 1.0 . |
+// overflow: | 'auto' 'hidden' . . | 'hidden' . . 'auto' |
+// display: | 'block' . . 'none' | 'none' 'block' . . |
+// Original: | | |
+// height: | 'auto' . . . | 'auto' . . . |
+// maxHeight: | '' . . . | '' . . . |
+// overflow: | 'auto' 'hidden' . . | 'hidden' . . 'auto' |
+// ------------------------+---------------------------------------------------------------------------------------+-----------------------------------------------------------------------------------+
+// Limit: | | |
+// Accordion: | | |
+// height: | Current . . . | Current . . . |
+// overflow: | 'hidden' . . . | 'hidden' . . . |
+// Wrapper: | | |
+// height: | 'auto' Remaining Remaining -> '0px' . | '0px' . '0px' -> Remaining 'auto' |
+// opacity: | 1.0 . 1.0 -> 0.0 . | 0.0 . 0.0 -> 1.0 . |
+// overflow: | 'auto' 'hidden' . . | 'hidden' . . 'auto' |
+// display: | 'block' . . 'none' | 'none' 'block' . . |
+// Original: | | |
+// height: | 'auto' . . . | 'auto' . . . |
+// maxHeight: | Remaining 'auto' . . | 'auto' . . Remaining |
+// overflow: | 'auto' 'hidden' . . | 'hidden' . . 'auto' |
+// ------------------------+---------------------------------------------------------------------------------------+-----------------------------------------------------------------------------------+
+// Fill: | | |
+// Accordion: | | |
+// height: | Current . . . | Current . . . |
+// overflow: | 'hidden' . . . | 'hidden' . . . |
+// Wrapper: | | |
+// height: | 'auto' . . . | 'auto' . . . |
+// opacity: | 1.0 . 1.0 -> 0.0 . | 0.0 . 0.0 -> 1.0 . |
+// overflow: | 'auto' 'hidden' . . | 'hidden' . . 'auto' |
+// display: | 'block' . . 'none' | 'none' 'block' . . |
+// Original: | | |
+// height: | Remaining . Remaining -> '0px' . | '0px' . '0px' -> Remaining . |
+// maxHeight: | '' . . . | '' . . . |
+// overflow: | 'auto' 'hidden' . . | 'hidden' . . 'auto' |
+// ------------------------+---------------------------------------------------------------------------------------+-----------------------------------------------------------------------------------+
+//
+// Although this table is rather large, it's extremely useful because it allows
+// us to present an informal argument for the correctness of our layout algorithm.
+// It's easy to verify that if an accordion starts in state 1 and proceeds through
+// steps 2, 3, and 4 when closing, it will arrive properly in state 5. Similarly
+// an accordion starting in state 5 that is opened will progress through steps 6, 7,
+// and 8 to arrive back at step 1. Clearly then any accordion starting in a valid
+// state of 1 or 5 can be opened or closed repeatedly and return to a proper state.
+// It's also worth pointing out that this layout algorithm can operate on one or two
+// panes at a time (i.e. if all panes were closed and one were clicked, it would
+// open a single pane... but if one pane were already open and another were clicked,
+// it would have to close one while opening the other).
+//
+// States 1 and 5 from the above table are implemented in the _intiailizeLayout and
+// _initializePane functions. States 2 and 6 are handled by the _startPaneChange
+// function while states 4 and 8 are handled by the _endPaneChange function. Finally,
+// states 3 and 7 are intialized and played in the _changePanes function.
+
+Sys.Extended.UI.AccordionBehavior = function(element) {
+ // The AccordionBehavior is used to turn properly structured XHTML into an
+ // Accordion with panes that can expand one at a time.
+ //
+ // "element" - the DOM element the behavior is associated with. It should contain an
+ // even number of child divs (such that ith pane has a header at div 2i and
+ // has content at div 2i+1).
+ Sys.Extended.UI.AccordionBehavior.initializeBase(this, [element]);
+
+ ///
+ /// The _selectedIndex variable is used to track the currently visible content
+ /// pane. It is persisted via ClientState so that it can be restored on PostBack.
+ /// If 0 <= _selectedIndex < _panes.Length is not true, then no pane is selected
+ /// (and they all appear collapsed). While any index outside the bounds of the
+ /// _panes array indicates that no pane is selected, we don't automatically set
+ /// the value to a sentinel like -1 (especially on the server) because it's
+ /// possible for additional panes to be added at any time. We abstract this
+ /// problem using the getPane() function which returns the selected pane when
+ /// it's called with no arguments (and returns null when the current selected
+ /// index is invalid).
+ ///
+ ///
get_selectedIndex
+ ///
set_selectedIndex
+ ///
+ this._selectedIndex = 0;
+
+ ///
+ /// Panes count.
+ ///
+ ///
getCount
+ ///
+
+ // The _panes array represents the collection of Accordion panes. Each element of
+ // the array is an object of the form {header, content, animation} corresponding
+ // to that pane's header section, content section, and the animation used to open
+ // and close its content section. The content element is a new div that has been
+ // created to wrap the original div (so we can completely collapse it - even if it
+ // has padding, margins, etc.) which is pointed to by a dynamic _original property.
+ // The header element has a dynamic _index property indicating its position in the
+ // Accordion's pane collection (used primarily by the headers' shared click handler).
+ // Furthermore, the animation will either be an instance of LengthAnimation or
+ // ParallelAnimation (in the latter case, it will have two children which are a
+ // LengthAnimation and a FadeAnimation). There will be two dynamic properties
+ // _length and _fade pointing to each of these children (to easily set the length
+ // and fadeEffect properties). There is also a dynamic _ended property which is
+ // an event handler to be fired when the animation is complete, a dynamic _opening
+ // property to indicate whether the animation was opening or closing the pane, and
+ // a dynamic _pane property to provide a reference to the pane that was being
+ // animated.
+ this._panes = [];
+
+ ///
+ /// Whether or not to fade the accordion panes when transitioning
+ ///
+ ///
get_fadeTransitions
+ ///
set_fadeTransitions
+ ///
+ this._fadeTransitions = false;
+
+ ///
+ /// Length of time to transition between Accordion sections in
+ /// milleseconds. The default value is 250ms.
+ ///
+ ///
get_transitionDuration
+ ///
set_transitionDuration
+ ///
+ this._duration = 0.25;
+
+ ///
+ /// A number of steps per second in transition animations.
+ /// The default value is 30 frames per second.
+ ///
+ ///
get_framesPerSecond
+ ///
set_framesPerSecond
+ ///
+ this._framesPerSecond = 30;
+
+ ///
+ /// Determines how to control resizing of the Accordion.
+ /// If it is set to None, then the Accordion can grow as large or as small as necessary.
+ /// If it is set to Limit, then the Accordion will always be less than or equal to its available space.
+ /// If it is set to Fill, then it will always be equal to its available space.
+ ///
+ ///
get_autoSize
+ ///
+ this._autoSize = Sys.Extended.UI.AutoSize.None;
+
+ ///
+ /// Whether or not clicking the header will close the currently opened pane (which
+ /// leaves all the Accordion's panes closed).
+ ///
+ ///
get_requireOpenedPane
+ ///
set_requireOpenedPane
+ ///
+ this._requireOpenedPane = true;
+
+ ///
+ /// Whether or not we suppress the client-side click handlers of any elements
+ /// (including server controls like Button or HTML elements like anchor) in the header
+ /// sections of the Accordion.
+ ///
+ ///
get_suppressHeaderPostbacks
+ ///
set_suppressHeaderPostbacks
+ ///
+ this._suppressHeaderPostbacks = false;
+
+ // Size of all the headers
+ this._headersSize = 0;
+
+ // The _headerClickHandler is a reference to the event handler that all the header
+ // elements of our panes will be wired up to
+ this._headerClickHandler = null;
+
+ ///
+ /// The _headerSelectedCssClass is the css class applied to the selected header.
+ ///
+ ///
get_headerCssClass
+ ///
set_headerCssClass
+ ///
+ this._headerCssClass = '';
+
+ ///
+ /// The _headerSelectedCssClass is the css class applied to the selected header.
+ ///
+ ///
get_headerSelectedCssClass
+ ///
get_headerSelectedCssClass
+ ///
+ this._headerSelectedCssClass = '';
+
+ // The _resizeHandler is a reference to the global event handler used to patch
+ // up the accordion when the window is resized
+ this._resizeHandler = null;
+
+ // Check the browser version and document mode to see if IE8 in standards-compliant mode is used
+ this._isIE8InStandardMode = (Sys.Browser.agent == Sys.Browser.InternetExplorer && Sys.Browser.version > 7 && Sys.Browser.documentMode != 0);
+}
+Sys.Extended.UI.AccordionBehavior.prototype = {
+ // The initialize function is responsible for getting the selected index from
+ // the ClientState mechanism and walking the children of the behavior's target
+ // to find all of the accordion's child panes. It builds up a collection of the
+ // panes from the headers and content sections. Then we hide all the content
+ // sections that aren't selected and initialize the layout.
+ initialize: function() {
+ // The initialize function is responsible for getting the selected index from
+ // the ClientState mechanism and walking the children of the behavior's target
+ // to find all of the accordion's child panes. It builds up a collection of the
+ // panes from the headers and content sections. Then we hide all the content
+ // sections that aren't selected and initialize the layout.
+ Sys.Extended.UI.AccordionBehavior.callBaseMethod(this, 'initialize');
+
+ // Create the onclick handler used by the accordion's headers
+ this._headerClickHandler = Function.createDelegate(this, this._onHeaderClick);
+
+ // Get the selected index from ClientState
+ var state = this.get_ClientState();
+ if(state !== null && state !== '') {
+ this._changeSelectedIndex(parseInt(state), false, true);
+ }
+
+ // Walk the children of the target control to obtain the accordion's child panes.
+ // We are expecting a hierarchy of divs that looks like:
+ //
this.element
+ // ...
+ // this.element.childNodes[2i]
+ //
this.element.childNodes[2i+1]
+ // ...
+ //
+ // We'll turn this hierarchy of divs into objects filling the _panes collection.
+ // See the comment above the _panes array for more details on the structure of
+ // these objects. It's also worth pointing out that we effectively "box" the
+ // index so it can be passed by reference to the _getNextDiv function
+ var nodes = this.get_element().childNodes;
+ var index = {};
+ for(index.value = 0; index.value < nodes.length; index.value++) {
+ var header = this._getNextDiv(nodes, index);
+ if(!header) {
+ break;
+ }
+ var content = this._getNextDiv(nodes, index);
+ if(content) {
+ // Add the pane once we've found both a header and a content section
+ // (but bump the loop index back so we don't skip an element when the
+ // loop increments)
+ this.addPane(header, content);
+ index.value--;
+ }
+ }
+
+ // Ensure we have an opened pane if we're required to (and use the first
+ // pane if we don't have a valid selected index)
+ if(this._requireOpenedPane && !this.getPane() && this._panes.length > 0) {
+ this._changeSelectedIndex(0, false, true);
+ }
+
+ // Setup the layout for the given AutoSize mode
+ this._initializeLayout();
+ },
+
+ _getNextDiv: function(nodes, index) {
+ // Get the next div in a sequence of child nodes starting at the
+ // given index
+ //
+ // "nodes" - array of child nodes (i.e. element.childNodes)
+ // "index" - The index is an object of the form { value } where index.value represents
+ // the current index in the collection of nodes. We wrap the index in an object
+ // to perform the .NET equivalent of boxing so it can be passed by reference.
+ var div = null;
+ while(index.value < nodes.length && (div = nodes[index.value++])) {
+ if(div.tagName && (div.tagName.toLowerCase() === 'div')) {
+ break;
+ }
+ }
+ return div;
+ },
+
+ ///
+ /// Create a new Accordion pane given references to its header and content divs
+ /// and add it to the _panes collection. We also wrap the content div in a new
+ /// container div, add a click handler to the header div, etc.
+ ///
+ ///
header element of the new Accordion pane
+ ///
content element of the new Accordion pane
+ ///
+ addPane: function(header, content) {
+ // New pane object added to the end of the Accordion's pane collection. The pane
+ // is an object of the form {header, content, animation} corresponding to that
+ // pane's header section, content section, and the animation used to open and
+ // close its content section. The content element is a new div that has been
+ // created to wrap the original div (so we can completely collapse it - even if it
+ // has padding, margins, etc.) which is pointed to by a dynamic _original property.
+ // The header element has a dynamic _index property indicating its position in the
+ // Accordion's pane collection (used primarily by the headers' shared click handler).
+ // Furthermore, the animation will either be an instance of LengthAnimation or
+ // ParallelAnimation (in the latter case, it will have two children which are a
+ // LengthAnimation and a FadeAnimation). There will be two dynamic properties
+ // _length and _fade pointing to each of these children (to easily set the length
+ // and fadeEffect properties). There is also a dynamic _ended property which is
+ // an event handler to be fired when the animation is complete, a dynamic _opening
+ // property to indicate whether the animation was opening or closing the pane, and
+ // a dynamic _pane property to provide a reference to the pane that was being
+ // animated.
+
+ // Create the new pane object
+ var pane = {};
+ pane.animation = null;
+
+ // Initialize the header
+ pane.header = header;
+ header._index = this._panes.length;
+ $addHandler(header, "click", this._headerClickHandler);
+
+ // Wrap the content in a new element
+ var accordion = this.get_element();
+ var wrapper = document.createElement('div');
+ accordion.insertBefore(wrapper, content);
+ wrapper.appendChild(content);
+ wrapper._original = content;
+ pane.content = wrapper;
+
+ // Remove any style facets (possibly) automatically applied by
+ // CSS selectors so they don't interfere with UI/layout
+ wrapper.style.border = '';
+ wrapper.style.margin = '';
+ wrapper.style.padding = '';
+
+ // Add the new pane at the bottom of the accordion
+ Array.add(this._panes, pane);
+
+ // Setup the layout attributes for the pane so that it will be in a proper opened or
+ // closed state (we don't bother setting the opacity of the wrapper with
+ // $common.setElementOpacity(wrapper, selected ? 1 : 0); because it will
+ // be taken care of by the animation)
+ this._initializePane(header._index);
+
+ // Since the content section of the accordion panes will be sent down from the server
+ // with display: none (so the content sections aren't shown before they're wrapped in
+ // new divs) we'll turn them back on once they've been wrapped in hidden divs
+ content.style.display = 'block';
+
+ return pane;
+ },
+
+ _getAnimation: function(pane) {
+ // Get the animation for the specified accordion section or demand create
+ // the animation if it doesn't already exist.
+ //
+ // "pane"- the pane is an object of the form {header, content, animation} corresponding to
+ // that pane's header section, content section, and the animation used to open and
+ // close its content section. The content element is a new div that has been created
+ // to wrap the original div (so we can completely collapse it - even if it has
+ // padding, margins, etc.) which is pointed to by a dynamic _original property. The
+ // header element has a dynamic _index property indicating its position in the
+ // Accordion's pane collection (used primarily by the headers' shared click
+ // handler). Furthermore, the animation will either be an instance of
+ // LengthAnimation or ParallelAnimation (in the latter case, it will have two
+ // children which are a LengthAnimation and a FadeAnimation). There will be two
+ // dynamic properties _length and _fade pointing to each of these children (to
+ // easily set the length and fadeEffect properties). There is also a dynamic _ended
+ // property which is an event handler to be fired when the animation is complete,
+ // a dynamic _opening property to indicate whether the animation was opening or
+ // closing the pane, and a dynamic _pane property to provide a reference to the pane
+ // that was being animated.
+
+ var animation = pane.animation;
+ if(!animation) {
+ // Determine whether or not to just use the length animation or build a
+ // composite effect with fading transitions (note that only the parent
+ // animation has the duration/fps specfied)
+ var length = null;
+ var fade = null;
+ if(!this._fadeTransitions) {
+ animation = length = new Sys.Extended.UI.Animation.LengthAnimation(pane.content, this._duration, this._framesPerSecond, "style", "height", 0, 0, "px");
+ } else {
+ length = new Sys.Extended.UI.Animation.LengthAnimation(null, null, null, "style", "height", 0, 0, "px");
+ fade = new Sys.Extended.UI.Animation.FadeAnimation(null, null, null, Sys.Extended.UI.Animation.FadeEffect.FadeOut, 0, 1, false);
+ animation = new Sys.Extended.UI.Animation.ParallelAnimation(pane.content, this._duration, this._framesPerSecond, [fade, length]);
+ }
+
+ // Create references to the length and fade animations so we can easily
+ // set the length and fadeEffect properties when animating without having
+ // to reach into the composite animation.
+ pane.animation = animation;
+ animation._length = length;
+ animation._fade = fade;
+ animation._pane = pane;
+ animation._opening = true;
+ animation._behavior = this;
+ animation._ended = Function.createDelegate(pane.animation, this._onAnimationFinished);
+ animation.add_ended(pane.animation._ended);
+
+ animation.initialize();
+ }
+ return animation;
+ },
+
+ _onAnimationFinished: function() {
+ // _onAnimationFinished is an event handler played after an animation (to open/
+ // close an accordion pane) has completed. The delegate for this function should
+ // have associated it with an animation (so the this references below are expecting
+ // to reach expando fields declared on the animation). It invokes _endPaneChange
+ // for the current pane.
+ this._behavior._endPaneChange(this._pane, this._opening);
+ },
+
+ _initializeLayout: function() {
+ // Setup the layout of the accordion (either when the behavior is created or when the
+ // AutoSize mode is changed).
+
+ // Stop any animations that are still playing (i.e. that haven't finished opening
+ // or closing from changing previous panes) in case the user changed the AutoSize
+ // mode while an animation was playing.
+ for(var i = 0; i < this._panes.length; i++) {
+ var animation = this._panes[i].animation;
+ if(animation && animation.get_isPlaying()) {
+ animation.stop();
+ }
+ }
+
+ // Cache the initial size of the accordion
+ var accordion = this.get_element();
+ this._initialHeight = accordion.offsetHeight;
+ var style = accordion.style;
+
+
+ // Initialize the accordion itself
+ if(this._autoSize === Sys.Extended.UI.AutoSize.None) {
+ // Remove the window resizing handler
+ this._disposeResizeHandler();
+
+ // IE7 appears to have a rendering quirk where it will hide the
+ // accordion if we change height or overflow from the empty string
+ // to auto when it's contained in a fixed size div. We'll just not
+ // change the value if that's the case.
+ var isIE7 = (Sys.Browser.agent == Sys.Browser.InternetExplorer && Sys.Browser.version >= 7)
+ if(!isIE7 || (isIE7 && style.height && style.height.length > 0)) {
+ style.height = 'auto';
+ }
+ if(!isIE7 || (isIE7 && style.overflow && style.overflow.length > 0)) {
+ style.overflow = 'auto';
+ }
+ } else {
+ // Add the window's resizing handler
+ this._addResizeHandler();
+
+ style.height = accordion.offsetHeight + 'px';
+ style.overflow = 'hidden';
+ }
+
+ // Setup the layout attributes for the pane so that it will be in a proper opened
+ // or closed state
+ for(var i = 0; i < this._panes.length; i++) {
+ this._initializePane(i);
+ }
+
+ // Resize the selected pane so (depending on the AutoSize mode) it will fill the
+ // available remaining space after the headers have been laid out.
+ this._resizeSelectedPane();
+ },
+
+ _initializePane: function(index) {
+ // Setup the layout attributes for the pane so that it will be in a proper opened or
+ // closed. This will be called when adding a new pane for the first time or when
+ // changing the AutoSize mode.
+ //
+ // "index" - index of the pane to initialize
+
+ var pane = this.getPane(index);
+ if(!pane) {
+ return;
+ }
+ var wrapper = pane.content;
+ var original = wrapper._original;
+
+ var opened = (index === this._selectedIndex);
+ wrapper.style.height = (opened || (this._autoSize === Sys.Extended.UI.AutoSize.Fill)) ? 'auto' : '0px';
+ wrapper.style.overflow = opened ? 'auto' : 'hidden';
+ wrapper.style.display = opened ? 'block' : 'none';
+ original.style.height = 'auto';
+ original.style.maxHeight = '';
+ original.style.overflow = opened ? 'auto' : 'hidden';
+
+ var opacity = (opened || !this._fadeTransitions) ? 1 : 0;
+ if(this._autoSize === Sys.Extended.UI.AutoSize.Fill) {
+ if($common.getElementOpacity(original) != opacity) {
+ $common.setElementOpacity(original, opacity);
+ }
+ if($common.getElementOpacity(wrapper) != 1) {
+ $common.setElementOpacity(wrapper, 1);
+ }
+ } else {
+ if($common.getElementOpacity(wrapper) != opacity) {
+ $common.setElementOpacity(wrapper, opacity);
+ }
+ if($common.getElementOpacity(original) != 1) {
+ $common.setElementOpacity(original, 1);
+ }
+ }
+
+ pane.header.className = this._selectedIndex === index ? (this._headerSelectedCssClass || this._headerCssClass) : this._headerCssClass;
+ },
+
+ _addResizeHandler: function() {
+ // Attach the resize handler
+ // This has been pulled out into its own method since we need to selectively wire
+ // up the resize handler depending on the AutoSize mode.
+
+ if(!this._resizeHandler) {
+ this._resizeHandler = Function.createDelegate(this, this._resizeSelectedPane);
+ $addHandler(window, "resize", this._resizeHandler);
+ }
+ },
+
+ dispose: function() {
+ // Remove the window resizing handler
+ this._disposeResizeHandler();
+
+ // Wipe all the animations
+ this._disposeAnimations();
+
+ // Wipe the _panes collection. We're careful to wipe any expando properties
+ // which could cause memory leaks in IE6.
+ for(var i = this._panes.length - 1; i >= 0; i--) {
+ var pane = this._panes[i];
+ if(pane) {
+ if(pane.header) {
+ pane.header._index = null;
+ $removeHandler(pane.header, "click", this._headerClickHandler);
+ pane.header = null;
+ }
+ if(pane.content) {
+ pane.content._original = null;
+ pane.content = null;
+ }
+ this._panes[i] = null;
+ delete this._panes[i];
+ }
+ }
+ this._panes = null;
+ this._headerClickHandler = null;
+
+ Sys.Extended.UI.AccordionBehavior.callBaseMethod(this, 'dispose');
+ },
+
+ _disposeResizeHandler: function() {
+ // Remove the resize handler
+ // This has been pulled out into its own method since we need to selectively wire
+ // up the resize handler depending on the AutoSize mode.
+
+ if(this._resizeHandler) {
+ $removeHandler(window, "resize", this._resizeHandler);
+ this._resizeHandler = null;
+ }
+ },
+
+ _disposeAnimations: function() {
+ // Dispose all the animations. This method was pulled out of dispose so we could
+ // allow the user to change the FadeTransitions property after the behavior was
+ // already initialized. We can merge it back into dispose once we support generic
+ // animations on the Accordion.
+
+ for(var i = 0; i < this._panes.length; i++) {
+ var animation = this._panes[i].animation;
+ if(animation) {
+ // Stop the animation if it was still playing
+ if(animation.get_isPlaying()) {
+ animation.stop();
+ }
+
+ // Remove the event handler that runs after the animation completes
+ if(animation._ended) {
+ animation.remove_ended(animation._ended);
+ animation._ended = null;
+ }
+
+ // Dispose the animation's resources (timer, etc.)
+ animation.dispose();
+
+ // Wipe expando properties to prevent leaks in IE6
+ animation._length = null;
+ animation._fade = null;
+ animation._pane = null;
+ animation._opening = null;
+ animation._behavior = null;
+ this._panes[i].animation = null;
+ }
+ }
+ },
+
+ _resizeSelectedPane: function() {
+ var pane = this.getPane();
+ if(!pane) {
+ return;
+ }
+
+ // Cache the header size so it only gets looked up when the window resizes
+ this._headersSize = this._getHeadersSize().height;
+
+ var original = pane.content._original;
+ switch(this._autoSize) {
+ case Sys.Extended.UI.AutoSize.None:
+ original.style.height = 'auto';
+ original.style.maxHeight = '';
+ break;
+ case Sys.Extended.UI.AutoSize.Limit:
+ var remaining = this._getRemainingHeight(false);
+ original.style.height = 'auto';
+ original.style.maxHeight = remaining + 'px';
+ break;
+ case Sys.Extended.UI.AutoSize.Fill:
+ var remaining = this._getRemainingHeight(true);
+ original.style.height = remaining + 'px';
+ original.style.maxHeight = '';
+ break;
+ }
+ },
+
+ _onHeaderClick: function(evt) {
+ // Get the header that raised the event (by walking up the event target's
+ // control hierarchy until we find an element just below the root of the accordion)
+ var header = evt.target;
+ var accordion = this.get_element();
+ while(header && (header.parentNode !== accordion)) {
+ header = header.parentNode;
+ }
+
+ // Stop the event from bubbling out of the header pane and prevent any
+ // action from happening unless we clicked a control inside the header
+ evt.stopPropagation();
+ if(this._suppressHeaderPostbacks) {
+ evt.preventDefault();
+ }
+
+ // Select this pane (which saves it to ClientState and initiates an animation
+ // to show the content pane). If the user clicked the currently open pane and
+ // we're not required to keep one pane open, then we'll set the selected index
+ // to -1 which will close it (which means all panes will be collapsed).
+ var index = header._index;
+ if((index === this._selectedIndex) && !this._requireOpenedPane) {
+ index = -1;
+ }
+ this._changeSelectedIndex(index, true);
+ },
+
+ _changeSelectedIndex: function(index, animate, force) {
+ // Change the accordion's selected pane to a new index (and optionally show the change).
+ //
+ // "index" - index of the new selected pane
+ // "animate" - whether or not to show the pane change (this is primarily intended to support
+ // restoring _selectedIndex in initialize before any panes have been added)
+ // "force" - we perform no action (i.e. raising events, animating, etc.) if the two indices represent
+ // the same pane (including the case when we have two different "no pane selected values"
+ // like -1 and -500). The force flag is used during initialization to skip this check since
+ // we aren't able to determine invalid values yet.
+
+ // Don't bother doing anything if the index didn't change (we actually check the
+ // panes so any invalid indices will match because getPane() will return null for
+ // both of them)
+ var lastIndex = this._selectedIndex;
+ var currentPane = this.getPane(index);
+ var lastPane = this.getPane(lastIndex);
+ if(!force && (currentPane == lastPane)) {
+ return;
+ }
+
+ // Raise the selectedIndexChanging event but don't change the selected index
+ // if the handler set the cancel property to true
+ var eventArgs = new Sys.Extended.UI.AccordionSelectedIndexChangeEventArgs(lastIndex, index);
+ this.raise_selectedIndexChanging(eventArgs);
+ if(eventArgs.get_cancel()) {
+ return;
+ }
+
+ //This sets the header CSS class to the non-selected case.
+ if(lastPane) {
+ lastPane.header.className = this._headerCssClass;
+ }
+
+ //This sets the selected header CSS class if available.
+ if(currentPane) {
+ currentPane.header.className = (this._headerSelectedCssClass == '') ?
+ this._headerCssClass : this._headerSelectedCssClass;
+ }
+
+ this._selectedIndex = index;
+
+ // Save the selected pane to preserve on postbacks
+ this.set_ClientState(this._selectedIndex);
+
+ // Animate the pane changes if required
+ if(animate) {
+ this._changePanes(lastIndex);
+ }
+
+ // Raise the selectedIndexChanged event and the propertyChanged event. We include
+ // both events because many users have expressed that the propertyChanged event is
+ // not discoverable.
+ this.raise_selectedIndexChanged(new Sys.Extended.UI.AccordionSelectedIndexChangeEventArgs(lastIndex, index));
+ this.raisePropertyChanged('SelectedIndex');
+
+ this._resizeSelectedPane();
+ },
+
+ _changePanes: function(lastIndex) {
+ // The _changePanes function is used to animate the change between two panes when
+ // the selected index changes. We will loop through each pane and get its
+ // animation (or demand create it if it doesn't have one yet), stop playing it if
+ // it's currently playing, change its parameters to either open or close, and then
+ // animate it. Because we have an animation for each pane and we stop them if they
+ // were already playing, the Accordion has the ability to nicely change panes
+ // again before the animation is finished.
+ //
+ // "lastIndex" - index of the last selected Accordion pane
+
+ if(!this.get_isInitialized()) {
+ return;
+ }
+
+ var open = null;
+ var close = null;
+ for(var i = 0; i < this._panes.length; i++) {
+ // Get the animation for each pane (creating it on demand if it doesn't
+ // already exist)
+ var pane = this._panes[i];
+ var animation = this._getAnimation(pane);
+
+ // Stop any animations that are still playing (i.e. that haven't finished
+ // opening or closing from changing previous panes)
+ if(animation.get_isPlaying()) {
+ animation.stop();
+ }
+
+ // If we're not opening or closing the current pane, then restart the loop.
+ // We set the _opening flag so we don't have to keep checking if we're opening
+ // or closing. If opening, we also set the display style of the pane's content
+ // so it will be visible for the animation
+ if(i == this._selectedIndex) {
+ animation._opening = true;
+ open = animation;
+ } else if(i == lastIndex) {
+ animation._opening = false;
+ close = animation;
+ } else {
+ continue;
+ }
+
+ // Get the pane ready to be animated by setting
+ this._startPaneChange(pane, animation._opening);
+
+
+ // Setup the fade effect if we are using it
+ if(this._fadeTransitions) {
+ animation._fade.set_effect(animation._opening ? Sys.Extended.UI.Animation.FadeEffect.FadeIn : Sys.Extended.UI.Animation.FadeEffect.FadeOut);
+ }
+
+ // Set the length animation to either open or close depending on whether or
+ // not this is the selected pane. We also change the target to be the wrapper
+ // or the original pane depending on whether the AutoSize mode is set to Fill
+ // (because we need the background color, etc., to grow with the animation
+ // which means changing the size of the original, not the wrapper, should grow).
+ // We would prefer to animate the wrapper because it can collapse all the way to
+ // 0px while the original can only collapse it's content size to 0px (leaving any
+ // padding, margins, borders, etc.) which is why we need to factor in the size of
+ // the original div's gutter pixels. Animating the original content will also
+ // cause the animation to jump slightly at the end when it collapses smoothly down
+ // to the gutter pixels but then sets display: none and disappears.
+ if(this._autoSize === Sys.Extended.UI.AutoSize.Fill) {
+ animation.set_target(pane.content._original);
+ animation._length.set_startValue($common.getContentSize(pane.content._original).height);
+ animation._length.set_endValue(animation._opening ? this._getRemainingHeight(true) : 0);
+ } else {
+ animation.set_target(pane.content);
+ animation._length.set_startValue(pane.content.offsetHeight);
+ animation._length.set_endValue(animation._opening ? this._getRemainingHeight(false) : 0);
+ }
+ }
+
+ // Play the animations to open the selected pane/close any other panes. Note that we
+ // pulled playing the animations out of the loop so that we could always play the
+ // closing animation first. If you play the opening animation first, in some cases the
+ // accordion will always grow slightly larger (shifting the rest of the page down) and
+ // then shrink again because the animations are running on different timers (and
+ // unifying them into a single animation would be a lot messier than the current
+ // implementation since they won't both play when we're closing the currently open pane).
+ if(close) {
+ close.play();
+ }
+ if(open) {
+ open.play();
+ }
+
+ // TODO: Someday get both animations to run on the same timer because it makes an obvious
+ // difference in IE6 and Safari
+ },
+
+ _startPaneChange: function(pane, opening) {
+ // Setup the pane before it is animated.
+
+ var wrapper = pane.content;
+ var original = wrapper._original;
+
+ if(opening) {
+ // Make the hidden panes visible so we can see them animate
+ wrapper.style.display = 'block';
+
+ // Workaround for IE8 to preserve the margins of the accordion headers
+ if(this._autoSize === Sys.Extended.UI.AutoSize.Fill && this._isIE8InStandardMode) {
+ original.style.display = 'block';
+ }
+ } else {
+ // Hide any overflow because we'll be shrinking the wrapper div down to 0px and
+ // we don't want content leaking out the bottom
+ wrapper.style.overflow = 'hidden';
+
+ // Turn off overflow on the original div because it's content doesn't grow during
+ // the animation and leaving it on slows the animation down
+ original.style.overflow = 'hidden';
+
+ // Remove any explicit height off the original content section but manually set
+ // the wrapper to the initial height (since it will be shrunk from this height
+ // to zero)
+ if(this._autoSize === Sys.Extended.UI.AutoSize.Limit) {
+ wrapper.style.height = this._getTotalSize(original).height + 'px';
+ original.style.maxHeight = '';
+ }
+ }
+ },
+
+ _endPaneChange: function(pane, opening) {
+ // Clean the pane up after it's been animated.
+
+ var wrapper = pane.content;
+ var original = wrapper._original;
+
+ if(opening) {
+ // Depending on the mode, move the explicit height value from the original
+ // content div to the wrapper div. This is necessary because we moved the
+ // explicit height value to the wrapper before the animation started since
+ // it was the target.
+ if(this._autoSize === Sys.Extended.UI.AutoSize.Limit) {
+ var remaining = this._getRemainingHeight(true);
+ original.style.maxHeight = remaining + 'px';
+ }
+
+ // Turn overflow back on so the original div's content can grow accordingly
+ original.style.overflow = 'auto';
+
+ // Remove an explicit height from the wrapper div so that it will
+ // automatically grow and shrink with the original content div
+ wrapper.style.height = 'auto';
+ wrapper.style.overflow = 'auto';
+ } else {
+ // If we finished a close animation, completely hide the pane so that
+ // it's content cannot be tabbed into
+
+ if(!this._isIE8InStandardMode) {
+ wrapper.style.display = 'none';
+ } else {
+ // Workaround for IE8 to preserve the margins of the accordion headers
+ if(this._autoSize === Sys.Extended.UI.AutoSize.Fill) {
+ original.style.display = 'none';
+ } else {
+ wrapper.style.height = '0px';
+ }
+ }
+ }
+ },
+
+ _getHeadersSize: function() {
+ // Compute the amount of space used by all the headers
+ var total = { width: 0, height: 0 };
+ for(var i = 0; i < this._panes.length; i++) {
+ var size = this._getTotalSize(this._panes[i].header);
+ total.width = Math.max(total.width, size.width);
+ total.height += size.height;
+ }
+ return total;
+ },
+
+ _getRemainingHeight: function(includeGutter) {
+ // Determine how much remaining height we have to fill with the currently selected
+ // pane's content section after taking into account all the headers. This is primarily
+ // used for the Limit and Fill AutoSize modes.
+ // Returns remaining height after all the headers have been accounted for.
+ //
+ // "includeGutter" - whether or not we should include the gutter (padding, borders, margins) of the
+ // selected pane's original content section. This should be true whenever we're
+ // getting the remaining height for the original content section and false whenever
+ // we're getting the remaining height for its wrapper.
+
+ var height = 0;
+ var pane = this.getPane();
+
+ if(this._autoSize === Sys.Extended.UI.AutoSize.None) {
+ // If the AutoSize mode is "None", then we use the size of the pane
+ if(pane) {
+ height = this._getTotalSize(pane.content._original).height;
+ }
+ } else {
+ // Compute the amount of space used
+ height = this._headersSize;
+ if(includeGutter && pane) {
+ height += this._getGutterSize(pane.content._original).height;
+ }
+
+ // Determine how much of the remaining space to use
+ // (if AutoSize is "Fill", use the rest of the available space)
+ var accordion = this.get_element();
+ height = Math.max(accordion.offsetHeight - height, 0);
+
+ // If AutoSize is "Limit", then the size of the pane should be either its
+ // actual size, or the rest of the available space.
+ if(pane && (this._autoSize === Sys.Extended.UI.AutoSize.Limit)) {
+ var required = this._getTotalSize(pane.content._original).height;
+ // Ensure we return a number greater than or equal to zero
+ if(required > 0) {
+ height = Math.min(height, required);
+ }
+ }
+ }
+
+ return height;
+ },
+
+ _getTotalSize: function(element) {
+ // Get the total size of an element, including its margins, in the form {width, height}
+
+ var size = $common.getSize(element);
+ var box = $common.getMarginBox(element);
+ size.width += box.horizontal;
+ size.height += box.vertical;
+ return size;
+ },
+
+ _getGutterSize: function(element) {
+ // Get the extra "gutter" size around an element made up of its padding,
+ // borders, and margins.
+ // Returns size of the extra space (in the form of {height, width})
+
+ var gutter = { width: 0, height: 0 };
+
+ try {
+ var box = $common.getPaddingBox(element);
+ gutter.width += box.horizontal;
+ gutter.height += box.vertical;
+ } catch(ex) { }
+
+ try {
+ var box = $common.getBorderBox(element);
+ gutter.width += box.horizontal;
+ gutter.height += box.vertical;
+ } catch(ex) { }
+
+ var box = $common.getMarginBox(element);
+ gutter.width += box.horizontal;
+ gutter.height += box.vertical;
+
+ return gutter;
+ },
+
+ ///
+ /// Fires when an Accordion pane is clicked, but before Accordion changes the pane.
+ ///
+ ///
+ ///
+ add_selectedIndexChanging: function(handler) {
+ this.get_events().addHandler('selectedIndexChanging', handler);
+ },
+ remove_selectedIndexChanging: function(handler) {
+ this.get_events().removeHandler('selectedIndexChanging', handler);
+ },
+ raiseSelectedIndexChanging: function(eventArgs) {
+ Sys.Extended.Deprecated("raiseSelectedIndexChanging(eventArgs)", "raise_selectedIndexChanging(eventArgs)");
+ this.raise_selectedIndexChanging(eventArgs);
+ },
+ raise_selectedIndexChanging: function(eventArgs) {
+ var handler = this.get_events().getHandler('selectedIndexChanging');
+ if(handler) {
+ handler(this, eventArgs);
+ }
+ },
+
+ ///
+ /// Fires when an Accordion pane is clicked, but after Accordion changes the pane.
+ ///
+ ///
+ ///
+ add_selectedIndexChanged: function(handler) {
+ this.get_events().addHandler('selectedIndexChanged', handler);
+ },
+ remove_selectedIndexChanged: function(handler) {
+ this.get_events().removeHandler('selectedIndexChanged', handler);
+ },
+ raiseSelectedIndexChanged: function(eventArgs) {
+ Sys.Extended.Deprecated("raiseSelectedIndexChanged(eventArgs)", "raise_selectedIndexChanged(eventArgs)");
+ this.raise_selectedIndexChanged(eventArgs);
+ },
+ raise_selectedIndexChanged: function(eventArgs) {
+ var handler = this.get_events().getHandler('selectedIndexChanged');
+ if(handler) {
+ handler(this, eventArgs);
+ }
+ },
+
+ ///
+ /// Get a specific Accordion pane given its index. If no index is provided, get the currently selected pane.
+ ///
+ ///
index of the desired Accordion pane. If the index is not provided, we use
+ /// the currently selected index. In the event the provided index (or the currently
+ /// selected index) is outside the bounds of the panes collection, we return null.
+ ///
+ getPane: function(index) {
+ if(index === undefined || index === null) {
+ index = this._selectedIndex;
+ }
+ return (this._panes && index >= 0 && index < this._panes.length) ? this._panes[index] : null;
+ },
+ get_Pane: function(index) {
+ Sys.Extended.Deprecated("get_Pane(index)", "getPane(index)");
+ return this.getPane(index);
+ },
+
+ ///
+ /// Panes count.
+ ///
+ ///
+ getCount: function() {
+ return this._panes ? this._panes.length : 0;
+ },
+ get_Count: function() {
+ Sys.Extended.Deprecated("get_Count()", "getCount()");
+ return this.getCount();
+ },
+
+ get_TransitionDuration: function() {
+ Sys.Extended.Deprecated("get_TransitionDuration()", "get_transitionDuration()");
+ return this.get_transitionDuration();
+ },
+ get_transitionDuration: function() {
+ return this._duration * 1000;
+ },
+ set_TransitionDuration: function(value) {
+ Sys.Extended.Deprecated("set_TransitionDuration(value)", "set_transitionDuration(value)");
+ this.set_transitionDuration(value);
+ },
+ set_transitionDuration: function(value) {
+ if(this._duration != (value / 1000)) {
+ this._duration = value / 1000;
+ for(var i = 0; i < this._panes.length; i++) {
+ var animation = this._panes[i].animation;
+ if(animation) {
+ animation.set_duration(this._duration);
+ }
+ }
+ this.raisePropertyChanged('TransitionDuration');
+ }
+ },
+
+ get_FramesPerSecond: function() {
+ Sys.Extended.Deprecated("get_FramesPerSecond()", "get_framesPerSecond()");
+ return this.get_framesPerSecond();
+ },
+ get_framesPerSecond: function() {
+ return this._framesPerSecond;
+ },
+ set_FramesPerSecond: function(value) {
+ Sys.Extended.Deprecated("set_FramesPerSecond(value)", "set_framesPerSecond(value)");
+ this.set_framesPerSecond(value);
+ },
+ set_framesPerSecond: function(value) {
+ if(this._framesPerSecond != value) {
+ this._framesPerSecond = value;
+ for(var i = 0; i < this._panes.length; i++) {
+ var animation = this._panes[i].animation;
+ if(animation) {
+ animation.set_fps(this._framesPerSecond);
+ }
+ }
+ this.raisePropertyChanged('FramesPerSecond');
+ }
+ },
+
+ get_FadeTransitions: function() {
+ Sys.Extended.Deprecated("get_FadeTransitions()", "get_fadeTransitions()");
+ return this.get_fadeTransitions();
+ },
+ get_fadeTransitions: function() {
+ return this._fadeTransitions;
+ },
+ set_FadeTransitions: function(value) {
+ Sys.Extended.Deprecated("set_FadeTransitions(value)", "set_fadeTransitions(value)");
+ return this.set_fadeTransitions(value);
+ },
+ set_fadeTransitions: function(value) {
+ if(this._fadeTransitions != value) {
+ this._fadeTransitions = value;
+
+ // Whenever we toggle FadeTransitions we dispose the entire list of
+ // animations because it's not easy to selectively play just one of a
+ // ParallelAnimation's children... This is of course just a temporary
+ // workaround until we enable generic animations and remove the
+ // FadeTransitions property.
+ this._disposeAnimations();
+
+ // Set all the content sections to 100% opacity in case they had been
+ // faded out when a pane was changed but not faded back in yet
+ if(!this._fadeTransitions) {
+ for(var i = 0; i < this._panes.length; i++) {
+ if($common.getElementOpacity(this._panes[i].content) != 1) {
+ $common.setElementOpacity(this._panes[i].content, 1);
+ }
+ if($common.getElementOpacity(this._panes[i].content._original) != 1) {
+ $common.setElementOpacity(this._panes[i].content._original, 1);
+ }
+ }
+ }
+ this.raisePropertyChanged('FadeTransitions');
+ }
+ },
+
+ get_HeaderCssClass: function() {
+ Sys.Extended.Deprecated("get_HeaderCssClass()", "get_headerCssClass()");
+ return this.get_headerCssClass();
+ },
+ get_headerCssClass: function() {
+ return this._headerCssClass;
+ },
+ set_HeaderCssClass: function(value) {
+ Sys.Extended.Deprecated("set_HeaderCssClass(value)", "set_headerCssClass(value)");
+ this.set_headerCssClass();
+ },
+ set_headerCssClass: function(value) {
+ this._headerCssClass = value;
+ this.raisePropertyChanged('HeaderCssClass');
+ },
+
+ get_HeaderSelectedCssClass: function() {
+ Sys.Extended.Deprecated("get_headerSelectedCssClass()", "get_headerSelectedCssClass()");
+ return this.get_selectedIndex();
+ },
+ get_headerSelectedCssClass: function(){
+ return this._headerSelectedCssClass;
+ },
+ set_HeaderSelectedCssClass: function(value) {
+ Sys.Extended.Deprecated("set_HeaderSelectedCssClass(value)", "set_headerSelectedCssClass(value)");
+ this.set_headerSelectedCssClass(value);
+ },
+ set_headerSelectedCssClass: function(value) {
+ this._headerSelectedCssClass = value;
+ this.raisePropertyChanged('HeaderSelectedCssClass');
+ },
+
+ get_AutoSize: function() {
+ Sys.Extended.Deprecated("get_AutoSize()", "get_autoSize()");
+ return get_autoSize();
+ },
+ get_autoSize: function() {
+ return this._autoSize;
+ },
+ set_AutoSize: function(value){
+ Sys.Extended.Deprecated("set_AutoSize(value)", "set_autoSize(value)");
+ this.set_autoSize(value);
+ },
+ set_autoSize: function(value) {
+ // In IE, treat AutoSize.Limit exactly the same as AutoSize.Fill. Since IE does not
+ // support the max-height CSS property, we can't achieve the Limit auto-size behavior
+ // without fixing the exact size of the pane (which means it can't grow/shrink in response
+ // to dynamic content changes). Since Fill has most of the same UI characteristics, it's
+ // a safer and cleaner workaround for this one browser.
+ if(Sys.Browser.agent === Sys.Browser.InternetExplorer && value === Sys.Extended.UI.AutoSize.Limit) {
+ value = Sys.Extended.UI.AutoSize.Fill;
+ }
+
+ if(this._autoSize != value) {
+ this._autoSize = value;
+ this._initializeLayout();
+ this.raisePropertyChanged('AutoSize');
+ }
+ },
+
+ get_SelectedIndex: function() {
+ Sys.Extended.Deprecated("get_SelectedIndex()", "get_selectedIndex()");
+ return this.get_selectedIndex();
+ },
+ get_selectedIndex: function() {
+ return this._selectedIndex;
+ },
+ set_SelectedIndex: function(value) {
+ Sys.Extended.Deprecated("set_SelectedIndex(value)", "set_selectedIndex(value)");
+ this.set_selectedIndex(value);
+ },
+ set_selectedIndex: function(value) {
+ this._changeSelectedIndex(value, true);
+ },
+
+ get_requireOpenedPane: function() {
+ return this._requireOpenedPane;
+ },
+ set_requireOpenedPane: function(value) {
+ if(this._requireOpenedPane != value) {
+ this._requireOpenedPane = value;
+ this.raisePropertyChanged('requireOpenedPane');
+ }
+ },
+
+ get_suppressHeaderPostbacks: function() {
+ return this._suppressHeaderPostbacks;
+ },
+ set_suppressHeaderPostbacks: function(value) {
+ if(this._suppressHeaderPostbacks != value) {
+ this._suppressHeaderPostbacks = value;
+ this.raisePropertyChanged('suppressHeaderPostbacks');
+ }
+ }
+}
+Sys.Extended.UI.AccordionBehavior.registerClass('Sys.Extended.UI.AccordionBehavior', Sys.Extended.UI.BehaviorBase);
\ No newline at end of file
diff --git a/packages/AjaxControlToolkit.StaticResources.15.1.4.0/Content/Scripts/AjaxControlToolkit/Debug/AjaxFileUpload.debug.js b/packages/AjaxControlToolkit.StaticResources.15.1.4.0/Content/Scripts/AjaxControlToolkit/Debug/AjaxFileUpload.debug.js
new file mode 100644
index 000000000..4e06d9cb7
--- /dev/null
+++ b/packages/AjaxControlToolkit.StaticResources.15.1.4.0/Content/Scripts/AjaxControlToolkit/Debug/AjaxFileUpload.debug.js
@@ -0,0 +1,1621 @@
+Type.registerNamespace("Sys.Extended.UI.AjaxFileUpload");
+
+// Utils
+Sys.Extended.UI.AjaxFileUpload.Utils = function() {
+
+ this.generateGuid = function() {
+ var result, i, j;
+ result = '';
+ for(j = 0; j < 32; j++) {
+ if(j == 8 || j == 12 || j == 16 || j == 20)
+ result = result + '-';
+ i = Math.floor(Math.random() * 16).toString(16).toUpperCase();
+ result = result + i;
+ }
+ return result;
+ };
+
+ this.getFileName = function(fullPath) {
+
+ var result = "";
+ if(!fullPath)
+ return '';
+
+ if(!fullPath.value && fullPath.name)
+ result = fullPath.name;
+ else {
+ if(!fullPath.value && typeof (fullPath) !== "string")
+ throw "Invalid parameter. fullPath parameter must be a string of full path or file element.";
+
+ if(fullPath.value)
+ fullPath = fullPath.value;
+
+ if(fullPath) {
+ var startIndex = (fullPath.indexOf('\\') >= 0 ? fullPath.lastIndexOf('\\') : fullPath.lastIndexOf('/'));
+ var filename = fullPath.substring(startIndex);
+ if(filename.indexOf('\\') === 0 || filename.indexOf('/') === 0) {
+ filename = filename.substring(1);
+ }
+
+ result = filename;
+ }
+ }
+
+ return encodeURIComponent(result);
+ };
+
+ this.getFileType = function(file) {
+ if(!file)
+ throw 'file must defined or not null';
+
+ if(!file.value && file.name)
+ return file.name.substring(file.name.lastIndexOf('.') + 1);
+
+ if(file.value)
+ file = file.value;
+
+ if(typeof (file) !== "string")
+ throw "can't resolve file type.";
+
+ return file.substring(file.lastIndexOf('.') + 1);
+ };
+
+ this.sizeToString = function(bytes) {
+ if(!bytes || bytes <= 0)
+ return '0 Kb';
+
+ var s = ['bytes', 'kb', 'MB', 'GB', 'TB', 'PB'];
+ var e = Math.floor(Math.log(bytes) / Math.log(1024));
+ return (bytes / Math.pow(1024, Math.floor(e))).toFixed(2) + " " + s[e];
+ };
+
+ this.checkHtml5BrowserSupport = function() {
+ return window.File
+ && window.FileReader && window.FileList
+ && window.Blob && (new XMLHttpRequest()).upload;
+ };
+
+};
+
+// Item
+Sys.Extended.UI.AjaxFileUpload.Item = function(parentId, fileItem, onRemoveItem) {
+ this._deleteButton = null;
+ this._parentId = parentId;
+ this._inputElementValue = fileItem.value;
+ this._id = fileItem.id;
+ this._slices = fileItem.slices;
+ this._sliceIndex = 0;
+
+ this._fileInfoContainer = null;
+ this._fileStatusText = null;
+ this._isUploaded = false;
+ this._isUploading = false;
+ this._fileSize = 0;
+ this._fileName = "";
+ this._fileType = "";
+ this._bytesUploaded = 0;
+
+ this._ui = this.initUI(onRemoveItem);
+};
+
+Sys.Extended.UI.AjaxFileUpload.Item.prototype = {
+ initUI: function(onRemoveItem) {
+
+ var self = this, file = this._inputElementValue, utils = new Sys.Extended.UI.AjaxFileUpload.Utils(),
+ isHtml5Support = utils.checkHtml5BrowserSupport(),
+
+ // generate unique id for each item
+ id = this._id,
+
+ // create line item container
+ container = $common.createElementFromTemplate({
+ nodeName: "div",
+ properties: {
+ id: this._parentId + '_FileItemContainer_' + id
+ },
+ cssClasses: ['ajax__fileupload_fileItemInfo']
+ }),
+
+ // create file info/status container
+ fileInfoContainer = $common.createElementFromTemplate({
+ nodeName: "div",
+ properties: {
+ id: this._parentId + '_FileInfoContainer_' + id,
+ style: {
+ display: 'inline-block'
+ }
+ }
+ }),
+
+ // create file info placeholder
+ fileInfoText = $common.createElementFromTemplate({
+ nodeName: "span",
+ properties: {
+ id: this._parentId + '_FileItemInfo_' + id
+ },
+ cssClasses: ['ajax__fileupload_fileItemInfo']
+ }),
+
+ // create file status placeholder
+ fileStatusText = $common.createElementFromTemplate({
+ nodeName: "span",
+ properties: {
+ id: this._parentId + '_FileItemStatus_' + id
+ },
+ cssClasses: ['uploadstatus']
+ }),
+
+ // init remove button
+ deleteButton = $common.createElementFromTemplate({
+ nodeName: "div",
+ properties: {
+ id: this._parentId + '_FileItemDeleteButton_' + id
+ },
+ cssClasses: ['removeButton']
+ });
+
+ this._fileName = utils.getFileName(file);
+ var fileNameToDisplay = decodeURIComponent(this._fileName);
+
+ if(isHtml5Support) {
+ this._fileSize = file.size;
+ var fType = file.type ? '
(' + file.type + ')' : '';
+ fileInfoText.innerHTML = '
' + fileNameToDisplay + ' '
+ + fType
+ + ' -
' + utils.sizeToString(file.size) + ' ';
+ this._fileType = file.type;
+ } else {
+
+ fileInfoText.innerHTML = '
' + fileNameToDisplay + '';
+ this._fileType = utils.getFileType(file);
+ }
+
+ fileInfoContainer.appendChild(fileInfoText);
+ fileInfoContainer.appendChild(fileStatusText);
+
+ $common.setText(deleteButton, Sys.Extended.UI.Resources.AjaxFileUpload_Remove);
+ $addHandlers(deleteButton, {
+ 'click': Function.createDelegate(this, function() {
+ onRemoveItem(self);
+ })
+ });
+
+ // build the line item
+ if(Sys.Browser.agent == Sys.Browser.InternetExplorer && Sys.Browser.version <= 8) {
+ container.appendChild(deleteButton);
+ container.appendChild(fileInfoContainer);
+ }
+ else {
+ container.appendChild(fileInfoContainer);
+ container.appendChild(deleteButton);
+ }
+
+
+ this._fileInfoContainer = fileInfoContainer;
+ this._deleteButton = deleteButton;
+ this._fileStatusText = fileStatusText;
+
+ return container;
+ },
+
+ setStatus: function(fileStatusText, text) {
+ $common.setText(this._fileStatusText, ' (' + text + ')');
+ this._fileInfoContainer.setAttribute('class', fileStatusText + 'State');
+ },
+
+ disabled: function(on) {
+ if(on)
+ this._deleteButton.disabled = 'disabled';
+ else
+ this._deleteButton.disabled = '';
+ },
+
+ hide: function() {
+ this._deleteButton.style.visibility = 'hidden';
+ },
+
+ destroy: function() {
+ $common.removeElement(this._inputElementValue);
+ $common.removeElement(this._deleteButton);
+ $common.removeElement(this._ui);
+ },
+
+ get_inputElementValue: function() {
+ return this._inputElementValue;
+ },
+
+ appendNodeTo: function(parent) {
+ parent.appendChild(this._ui);
+ },
+
+ removeNodeFrom: function(parent) {
+ parent.removeChild(this._ui);
+ }
+};
+
+// Processor
+Sys.Extended.UI.AjaxFileUpload.Processor = function(control, elements) {
+
+ var utils = new Sys.Extended.UI.AjaxFileUpload.Utils(),
+ xhrPoll = new XMLHttpRequest();
+
+ this._iframe = null;
+ this._iframeName = control.get_id() + '_uploadIframe';
+ this._form = null;
+
+ this.initialize = function() {
+ this.attachEvents();
+ this.createIFrame();
+ this.createForm();
+ };
+
+ this.attachEvents = function() {
+ this.onFileSelected$delegate = Function.createDelegate(this, this.onFileSelectedHandler);
+ this.attachFileInputEvents(elements.inputFile, true);
+
+ var self = this;
+
+ // Capture uploading percentage report from server
+ xhrPoll.onreadystatechange = function(e) {
+ if(xhrPoll.readyState == 4) {
+
+ if(xhrPoll.status == 200) {
+
+ // Update percentage
+ var percent = xhrPoll.responseText;
+ if(percent) {
+ percent = parseFloat(percent).toFixed(2);
+ control.setPercent(percent);
+ }
+
+ // Keep polling as long as upload is not completed
+ if(percent < 100)
+ setTimeout(function() {
+ self.pollingServerProgress(true);
+ }, 500);
+
+ } else {
+
+ // We won't do any error handler for polling, since upload error handled by upload request it self.
+
+ }
+ }
+ };
+ };
+
+ this.attachFileInputEvents = function(fileInput, attach) {
+ if(attach)
+ $addHandlers(fileInput, { 'change': this.onFileSelected$delegate });
+ else
+ $common.removeHandlers(fileInput, { 'change': this.onFileSelected$delegate });
+ },
+
+ this.onFileSelectedHandler = function(e) {
+ // User selects file through browser open file dialog.
+ // We generate file item and add it into file list, and recreate new element for next file.
+
+ // generate file item to be uploaded
+ var fileItem = {
+ id: utils.generateGuid(),
+ value: elements.inputFile,
+ type: utils.getFileType(elements.inputFile.value)
+ };
+
+
+ if(control.fileTypeIsValid(fileItem.type)) {
+ control.addFileToQueue(fileItem);
+ this.createInputFileElement();
+ } else {
+ control.confirmFileIsInvalid(fileItem);
+ }
+
+ };
+
+ this.createInputFileElement = function() {
+ var currentInputFile = elements.inputFile;
+
+ // disfunction current input file element
+ currentInputFile.style.zIndex = -999;
+ $common.setLocation(currentInputFile, { x: -99999, y: -99999 });
+ this.attachFileInputEvents(currentInputFile, false);
+
+ // create new input file element in same location
+ var id = control.get_id() + '_file_' + utils.generateGuid(),
+ newInputFile = $common.createElementFromTemplate({
+ nodeName: 'input',
+ properties: {
+ id: id,
+ name: "act-file-data",
+ type: 'file',
+ style: {
+ zIndex: 0,
+ cursor: 'pointer',
+ position: 'absolute'
+ }
+ }
+ }, currentInputFile.parentNode);
+
+ $common.setElementOpacity(newInputFile, 0);
+ this.attachFileInputEvents(newInputFile, true);
+
+ // set current input file with the new one
+ elements.inputFile = newInputFile;
+ };
+
+ this.startUpload = function() {
+ // Get un-uploaded file on the top from queue and start upload it.
+
+ var form = this._form,
+ fileItem = control.getNextFile();
+
+ if(!fileItem) {
+ control._currentFileId = null;
+ this.setThrobber(false);
+ control.done();
+ return;
+ }
+
+ // set file item status
+ control.setAsUploading(fileItem);
+
+ var inputElement = fileItem.get_inputElementValue();
+
+ // set current file id
+ control._currentFileId = fileItem._id;
+
+ this.setThrobber(true);
+
+ // clear all form children
+ while(form.firstChild) {
+ form.removeChild(form.firstChild);
+ }
+
+ inputElement.name = 'act-file-data';
+
+ // only add 1 file input element to be uploaded
+ form.appendChild(inputElement);
+ form.setAttribute("action", control._uploadUrl + '?contextKey=' + control.get_id() + '&fileId=' + control._currentFileId + '&fileName=' + fileItem._fileName + '&usePoll=' + (control.get_serverPollingSupport() ? "true" : "false"));
+
+ // upload it now
+ form.submit();
+ };
+
+ this.cancelUpload = function() {
+ // send message to server to cancel this upload
+ var xhr = new XMLHttpRequest(),
+ self = this;
+
+ // aborting server polling request
+ if(xhrPoll)
+ xhrPoll.abort();
+
+ xhr.open("POST", '?contextKey=' + control.get_id() + "&cancel=1&guid=" + control._currentFileId, true);
+ xhr.onreadystatechange = function() {
+ self.setThrobber(false);
+ if(xhr.readyState == 4) {
+ if(xhr.status == 200) {
+ control.cancelUpload();
+ } else {
+ // cancelation is error.
+ self.raiseUploadError(xhr);
+ throw "Failed to cancel upload.";
+ }
+ }
+ };
+ xhr.send(null);
+ },
+
+ this.createIFrame = function() {
+
+ var name = this._iframeName,
+ iframe = document.createElement("IFRAME");
+
+ iframe.width = "0";
+ iframe.height = "0";
+ iframe.style.display = "none";
+ iframe.src = "about:blank";
+ iframe.id = name;
+ iframe.name = name;
+ iframe.security = "restricted";
+ document.body.appendChild(iframe);
+ iframe.contentWindow.name = name;
+ $addHandlers(iframe, {
+ load: Function.createDelegate(this, this.onIFrameLoadedHandler)
+ });
+
+ this._iframe = iframe;
+ };
+
+ this.onIFrameLoadedHandler = function(e) {
+ // Event handler to capture when iframe receive response from server.
+
+ if(!control._currentFileId)
+ return;
+
+ try {
+
+ var iframe = this._iframe, doc = null;
+
+ // Only test the iframe data, exception should thrown if something went wrong.
+ if(iframe.contentDocument)
+ // Firefox, Opera
+ doc = iframe.contentDocument;
+ else if(iframe.contentWindow)
+ // Internet Explorer
+ doc = iframe.contentWindow.document;
+ else if(iframe.document)
+ // Others?
+ doc = iframe.document;
+
+ if(doc == null)
+ throw "Document not initialized";
+
+ // finalizing and upload next file
+ control.doneAndUploadNextFile(control.getCurrentFileItem());
+
+ } catch(e) {
+
+ // Cancelation / aborting upload can causing 'Access is denied' or 'Permission denied' on IE 9 bellow,
+ // let's consider this exception is not trully error exception from server.
+ if(!control._canceled || !(e.message && (e.message.indexOf("Access is denied") > -1 || e.message.indexOf("Permission denied") > -1))) {
+ this.raiseUploadError(e);
+ throw e;
+ }
+ }
+ };
+
+ this.setThrobber = function(value) {
+ // Show or hide throbber when processing upload.
+
+ if(control.get_serverPollingSupport()) {
+ control.setPercent(0);
+ $common.setVisible(elements.progressBar, value ? true : false);
+ $common.setVisible(elements.progressBarContainer, value ? true : false);
+ this.pollingServerProgress(value);
+ return;
+ }
+
+ if(control.get_throbber() != null) {
+ control.get_throbber().style.display = value ? "" : "none";
+ }
+ };
+
+ this.pollingServerProgress = function(value) {
+ // Get percentage of uploading progress from server.
+
+ if(!value || !control._currentFileId)
+ return;
+
+ xhrPoll.open("GET", '?contextKey=' + control.get_id() + "&poll=1&guid=" + control._currentFileId, true);
+ xhrPoll.send(null);
+ };
+
+ this.createForm = function() {
+ // Create form that we use to upload file one by one taken from queue.
+
+ var form,
+ formId = "___postForm" + control.get_id();
+
+ try {
+ form = document.createElement('