// Place your application-specific JavaScript functions and classes here
// This file is automatically included by javascript_include_tag :defaults

// http://alistapart.com/articles/zebratables/

  // this function is need to work around 
  // a bug in IE related to element attributes
  function hasClass(obj) {
     var result = false;
     if (obj.getAttributeNode("class") != null) {
         result = obj.getAttributeNode("class").value;
     }
     return result;
  }   

 function stripe(id) {

    // the flag we'll use to keep track of 
    // whether the current row is odd or even
    var even = false;
  
    // if arguments are provided to specify the colours
    // of the even & odd rows, then use the them;
    // otherwise use the following defaults:
    var evenColor = arguments[1] ? arguments[1] : "#fff";
    var oddColor = arguments[2] ? arguments[2] : "#eee";
  
    // obtain a reference to the desired table
    // if no such table exists, abort
    var table = document.getElementById(id);
    if (! table) { return; }
    
    // by definition, tables can have more than one tbody
    // element, so we'll have to get the list of child
    // &lt;tbody&gt;s 
    var tbodies = table.getElementsByTagName("tbody");

    // and iterate through them...
    for (var h = 0; h < tbodies.length; h++) {
    
     // find all the &lt;tr&gt; elements... 
      var trs = tbodies[h].getElementsByTagName("tr");
      
      // ... and iterate through them
      for (var i = 0; i < trs.length; i++) {

	    // avoid rows that have a class attribute
        // or backgroundColor style
	    if (!hasClass(trs[i]) && ! trs[i].style.backgroundColor) {
 
         // get all the cells in this row...
          var tds = trs[i].getElementsByTagName("td");
        
          // and iterate through them...
          for (var j = 0; j < tds.length; j++) {
        
            var mytd = tds[j];

            // avoid cells that have a class attribute
            // or backgroundColor style
	        if (! hasClass(mytd) && ! mytd.style.backgroundColor) {
        
		      mytd.style.backgroundColor = even ? evenColor : oddColor;
              
            }
          }
        }
        // flip from odd to even, or vice-versa
        even =  ! even;
      }
    }
  }
 
// end http://alistapart.com/articles/zebratables/

// http://www.quirksmode.org/js/findpos.html

function findPos(obj) {
    var curleft = curtop = 0;
    if (obj.offsetParent) {
        curleft = obj.offsetLeft;
        curtop = obj.offsetTop;
        while (obj = obj.offsetParent) {
            curleft += obj.offsetLeft;
            curtop += obj.offsetTop;
        }
    }
    return [curleft,curtop];
}

// end http://www.quirksmode.org/js/findpos.html

function stripeList(id) {
    // the flag we'll use to keep track of 
    // whether the current row is odd or even
    var even = false;
    
    // if arguments are provided to specify the colours
    // of the even & odd rows, then use the them;
    // otherwise use the following defaults:
    var evenColor = arguments[1] ? arguments[1] : "#dddddd";
    var oddColor = arguments[2] ? arguments[2] : "#f5f5f5";
    
    // obtain a reference to the desired table
    // if no such table exists, abort
    var list = $(id);
    if (!list) { return; }
    // find all the &lt;tr&gt; elements... 
    var rows = list.childNodes;
    
    // ... and iterate through them
    for (var i = 0; i < rows.length; i++) {
        var row = rows[i];
        if (!row.tagName || row.tagName.toLowerCase() != 'li') {
            continue;
        }
        
        // avoid rows that have a class attribute
        // or backgroundColor style
        if (!hasClass(rows[i]) && ! rows[i].style.backgroundColor) {
            // get all the cells in this row...
            row.style.backgroundColor = even ? evenColor : oddColor;
        }
        
        // flip from odd to even, or vice-versa
        even =  ! even;
    }
}

function stripeContainer(element, options) {
    element = $(element);
    var tagName = element.tagName.toLowerCase();
    if (tagName == 'ul') {
        var even = false;
        for (var i = 0; i < element.childNodes.length; ++i) {
            var child = $(element.childNodes[i]);
            if (child.tagName && child.visible()) {
                stripeRow(child, even, options);
                even = !even;
            }
        }
    } else if (tagName == 'table') {
        for (var i = 0; i < element.childNodes.length; ++i) {
            var tbody = element.childNodes[i];
            if (!tbody.tagName || tbody.tagName.toLowerCase() != 'tbody') {
                continue;
            }
            var even = false;
            for (var j = 0; j < tbody.childNodes.length; ++j) {
                var tr = $(tbody.childNodes[j]);
                if (tr.tagName && tr.tagName.toLowerCase() == 'tr' && tr.visible()) {
                    if (!tr.hasClassName('nostripe')) {
                        stripeRow(tr, even, options);
                    }
                    even = !even;
                }
            }
        }
    }
}

function stripeRow(row, even, options) {
    if (even) {
        if (options.evenClass) {
            row.addClassName(options.evenClass);
        }
        if (options.oddClass) {
            row.removeClassName(options.oddClass);
        }
    } else {
        if (options.oddClass) {
            row.addClassName(options.oddClass);
        }
        if (options.evenClass) {
            row.removeClassName(options.evenClass);
        }
    }
}

function getMousePosition(event) {
    var x = 0, y = 0;
    if (!event) {
        event = window.event;
    }
    if (event.pageX || event.pageY) {
        x = event.pageX;
        y = event.pageY;
    } else if (event.clientX || event.clientY) {
        x = event.clientX;
        y = event.clientY;
        if (Prototype.Browser.IE) {
            x += document.documentElement.scrollLeft;
            y += document.documentElement.scrollTop;
        }
    }
    return [x, y];
}

function removeClosestRemovable(obj) {
    while (obj != null) {
        if (Element.hasClassName(obj, 'removable')) {
            Element.remove(obj);
            break;
        }
        obj = obj.parentNode;
    }
}

function OptionsViewer() {
}

OptionsViewer.prototype = {
    showOptions: function(table, productId, managing) {
        var action = managing ? 'x' : 'show';
        new Ajax.Request('/products/'+action+'/'+productId, {asynchronous: true, evalScripts: true, onComplete: function(req) {
            $('product_'+productId).innerHTML = req.responseText;
        }});
        new Ajax.Updater('product_details', '/stores/show_product_details/'+productId, { asynchronous: true, evalScripts: true });
        this.openProductId = productId;
    },
    
    hideOptions: function(managing) {
        if (this.openProductId) {
            var action = managing ? 'manage_collapsed' : 'show_collapsed';
            new Ajax.Updater('product_'+this.openProductId, '/products/'+action+'/'+this.openProductId, {asynchronous: true, evalScripts: true});
            this.openProductId = null;
        }
    },
    
    toggleOptions: function(table, productId, managing) {
        if (this.openProductId && this.openProductId == productId) {
            this.hideOptions(managing);
        } else {
            this.hideOptions(managing);
            this.showOptions(table, productId, managing);
        }
        stripeList('products');
    }
};

// tagNames can be a string for one tag name or an array for a bunch of tag names
function getAncestorByTagName(tagNames, element) {
    if (tagNames instanceof Array) {
        for (var i = 0; i < tagNames.length; ++i) {
            tagNames[i] = tagNames[i].toLowerCase();
        }
    } else {
        tagNames = [ tagNames.toLowerCase() ];
    }
    do {
        element = element.parentNode;
    } while (element && element.tagName && !tagNames.include(element.tagName.toLowerCase()));
    return element;
}

function getAncestorByClassName(className, element) {
    do {
        element = element.parentNode;
    } while (element && !Element.hasClassName(element, className));
    return element;
}

function getIframeDocument(iframe) {
    // see http://developer.apple.com/internet/webcontent/iframe.html
    return iframe.contentDocument || iframe.document || iframe.contentWindow.document;
}

function onProductSubmit() {
    // use frames['frame-name'] to access frame in ie properly
    $('category_spot').innerHTML = getIframeDocument(frames['upload']).getElementById('replacement').innerHTML;
    // currently upload html doesn't contain any script tags, and it really shouldn't contain any ever
    //var text = getIframeDocument(frames['upload']).getElementById('replacement').innerHTML;
    //$('category_spot').innerHTML = text.replace(/<script.*?<\/script>/i, '');
}

function createPopup(id) {
  var popup = $(id);
  if (popup) {
    popup.innerHTML = '';
  } else {
    popup = new Element('div', {id: id, className: 'popup'});
    $('popup-insert-marker').insert({before: popup});
  }
  popup.hide();
  return popup;
}

function showImageFromUrl(url, product) {
    var popup = $('image-popup');
    var title = $('image-popup-title');
    title.innerHTML = '';
    title.appendChild(document.createTextNode(product));
    var popupImage = $('image-popup-image');
    popupImage.src = url;
    if (Prototype.Browser.IE) {
        $('measure').innerHTML = "<img src=\""+url+"\" />";
        var ieImage = $('measure').childNodes[0];
        Event.observe(ieImage, 'load', function() { Popup.showCenteredImageSized(popup, ieImage, {clickAction: 'hide'}); });
    } else {
        Event.observe(popupImage, 'load', function() { Popup.showCenteredImageSized(popup, popupImage, {clickAction: 'hide'}); });
    }
}

function showImage(image, product) {
    showImageFromUrl(image.src, product);
}

function centerPopup(popup) {
    popup = $(popup);
    popup.style.position = 'absolute';
    popup.style.left = (document.body.offsetWidth-popup.offsetWidth)/2 + 'px';
    popup.style.top = ((window.innerHeight || document.body.offsetHeight)-popup.offsetHeight-60)/2 + 'px';
}

function cssQueryFirst() {
    var element = cssQuery.apply(this, arguments)[0];
    if (element) {
        element = $(element);
    }
    return element;
}

var cacheStore = {};
function cache(key, valueFn) {
    var value = cacheStore[key];
    if (value == undefined) {
        cacheStore[key] = value = valueFn();
    }
    return value;
}

function firstRealChild(element) {
    var children = element.childNodes;
    for (var i = 0; i < children.length; ++i) {
        if (children[i].tagName) {
            return children[i];
        }
    }
    throw "First real child is missing from "+element;
}

function currency(value) {
    value = value.replace(/[^0-9.]/, '');
    value = value.replace(/^(\.\d\d).+/, '\1');
    return value ? "<span class='twelveblue'>$"+value+"</span>" : '';
}

function setOptionKind(option, kind, input) {
    setTimeout(function() {
        option = option.toString();
        var li = getAncestorByTagName('li', input);
        var description = cssQueryFirst('.option-description', li).value;
        description = "<span class='description'>"+description.escapeHTML()+"</span>";
        var price = cssQueryFirst('.option-price', li).value;
        description += " <span class='price'>"+currency(price)+"</span>";
        var html;
        switch (kind) {
        case 'heading':
            html = description;
            break;
        case 'checkbox':
            html = ' <input type="checkbox">'+description;
            break;
        case 'radio':
            html = ' <input type="radio">'+description;
            break;
        case 'numeric':
            html = ' <input type="text" size="5">'+description;
        }
        $('option_'+option+'-preview').innerHTML = html;
        if (Prototype.Browser.IE) {
            var form = getAncestorByTagName('form', li);
            form["kind:"+option] = kind;
        }
    }, 20);
}

function addCuisine() {
    var name = $('name').value;
    var p = document.createElement('div');
    p.className = 'removable';
    p.innerHTML = '<strong>'+name+"</strong> <a href='#' onclick='removeClosestRemovable(this);return false'>Remove</a><input type='hidden' name='cuisines[]' value='"+name+"'>";
    $('cuisines').appendChild(p);
    Sortable.create('cuisines', {tag: 'div'});
}

function updateQuantity(productId, element) {
    new Ajax.Updater('check', '/cart/update_quantity/'+productId, { asynchronous: true, evalScripts: true, parameters: 'quantity='+element.value, onComplete: function() {
    }});
}

function serializeParameters(parameters) {
    var str = '';
    for (var key in parameters) {
        str += '&'+escape(key)+'='+escape(parameters[key]);
    }
    return str.substring(1);
}

function tryAddToCart(link, returnPath) {
    var form = link.form;
    var deliveryTime = $('delivery-time');
    if (deliveryTime && deliveryTime.positivelyVisible()) {
        alert($('delivery-time-message').innerHTML);
        setTimeout(function() { link.disabled = false; }, 100);
        return false;
    }
    for (var i = 0; i < form.elements.length; ++i) {
        var element = form.elements[i];
        if (element.name == 'item[product_id]') {
            var url = '/cart/add';
            if (returnPath) {
              url += '?return_path='+escape(returnPath);
            }
            new Ajax.Request(url, { parameters: Form.serialize(form), evalScripts: true} );
        }
    }
}

function enableCheckoutTime(enabled) {
    $$('#checkout-time input, #checkout-time select').each(function(element) {
        element.disabled = !enabled;
    });
}

function overrideCheckoutOnSubmit(updateElement, url) {
    $('main-form').onsubmit = function() {
        new Ajax.Updater(updateElement, url, { asynchronous: true, evalScripts: true, parameters: Form.serialize(this), onSuccess: function() {
            $('main-form').onsubmit = null;
        }});
        return false;
    }
}

function updateOptionsOrder() {
    order = Sortable.serialize('options');
    // XXX this is hackish; 'options' key is already used for the actual option values
    order = order.replace(/[^\d]+/g, ',').substr(1);
    $('options_order').value = order;
}

function updateOptionsPreviewOrder(element) {
    var previewElement = $(element.id+'-preview');
    var previewParent = previewElement.parentNode;
    previewParent.removeChild(previewElement);
    var destination = element.nextSibling;
    while (destination && !destination.tagName) {
        destination = destination.nextSibling;
    }
    if (destination) {
        var previewDestination = $(destination.id+'-preview');
        previewParent.insertBefore(previewElement, previewDestination);
    } else {
        previewParent.appendChild(previewElement);
    }
    if (Prototype.Browser.IE) {
        var form = getAncestorByTagName('form', element);
        var option = element.id.replace(/[^0-9]/g, '');
        if (form["kind:"+option] != undefined) {
            cssQuery('input.option-kind', element).each(function(kindElement) {
                kindElement.checked = kindElement.value == form["kind:"+option];
            });
        }
    }
}

function rebuildOptionsSortable() {
    Sortable.create("options", { onUpdate: updateOptionsOrder, onChange: updateOptionsPreviewOrder });
}

function rebuildProductsSortable(categoryId) {
    Sortable.create('products', { onUpdate: function() {
        new Ajax.Request('/categories/update_products_order/'+categoryId, { parameters: Sortable.serialize('products'), asynchronous: true, evalScripts: true });
    }});
}

function radioOptionClicked(name, element) {
    var radios = $$(".option-"+name);
    for (var i = 0; i < radios.length; ++i) {
        if (radios[i].name != element.name) {
            radios[i].checked = false;
        }
    }
}

function selectCategory(id, addToHistory) {
    var parent = $('categories');
    for (var i = 0; i < parent.childNodes.length; ++i) {
        var child = parent.childNodes[i];
        if (!child.tagName || child.tagName.toLowerCase() != 'li') {
            continue;
        }
        if (child.id != "category_"+id.toString()) {
            Element.removeClassName(child, 'selected');
        } else {
            Element.addClassName(child, 'selected');
        }
    }
    onCategoryShown(id, false, false);
    if (addToHistory) {
        dhtmlHistory.add('category-'+id, {category: id});
    }
}

var FormSaver = {
    saveAndClearElements: function() {
        if (!this.savedElements) {
            this.savedElements = [];
        }
        for (var i = 0; i < arguments.length; ++i) {
            var element = $(arguments[i]);
            // currently only text and checkbox elements are supported
            var type = element.type.toLowerCase();
            if (type == 'text' || type == 'textarea') {
                this.savedElements[element.name] = element.value;
            } else if (type == 'checkbox') {
                this.savedElements[element.name] = element.checked;
            } else {
                alert("Don't know how to save element of type "+type);
            }
        }
    },
    
    restoreElementsIfSaved: function() {
        if (this.savedElements) {
            for (var i = 0; i < arguments.length; ++i) {
                var element = $(arguments[i]);
                if (this.savedElements[element.name]) {
                    var type = element.type.toLowerCase();
                    if (type == 'text' || type == 'textarea') {
                        element.value = this.savedElements[element.name];
                    } else if (type == 'checkbox') {
                        element.checked = this.savedElements[element.name];
                    } else {
                        alert("Don't know how to restore element of type "+type);
                    }
                    break;
                }
            }
        }
    }
};

function toggleLimitedQuantity(link) {
    var form = Object.extend(getAncestorByTagName('form', link), FormSaver);
    var container = $('available-quantity');
    if (!container.visible()) {
        form.restoreElementsIfSaved('product_available_quantity', 'product_show_available_quantity');
        container.show();
    } else {
        container.hide();
        form.saveAndClearElements('product_available_quantity', 'product_show_available_quantity');
    }
}

function onCategoryShown(categoryId, manage, createdOrUpdated) {
    parent.optionsViewer = new OptionsViewer();
    if (manage) {
        if (createdOrUpdated) {
            parent.onProductSubmit();
        }
        parent.rebuildProductsSortable(categoryId);
    }
    stripeList('products');
}

function addHiddenInput(form, name, value) {
    var input = document.createElement('input');
    input.name = name;
    input.value = value;
    input.type = 'hidden';
    input.className = 'autogenerated';
    form.appendChild(input);
}

function buildOptionHashFromForm(optionGroup) {
    var hash = {};
    [ 'kind', 'name', 'description', 'price' ].each(function(field) {
        var value;
        if (field == 'kind') {
            cssQuery('.option-'+field, optionGroup).each(function(radio) {
                if (radio.checked) {
                    value = radio.value;
                }
            });
        } else {
            value = cssQueryFirst('.option-'+field, optionGroup).value;
        }
        if (field == 'price' && value == '') {
            value = 0;
        }
        hash[field] = value;
    });
    return hash;
}

function buildOptionMatrixHashFromForm(optionGroup) {
    var hash = { rows: [], columns: [] }
    var matrixTable = cssQueryFirst('table.option-matrix', optionGroup);
    if (matrixTable) {
        var rows = matrixTable.rows;
        // columns
        var row = rows[0];
        // first cell is blank
        for (var i = 1; i < row.cells.length; ++i) {
            var cell = row.cells[i];
            var text = cell.firstChild.data ? cell.firstChild.data : cell.firstChild.value;
            hash.columns.push(text);
        }
        // rows
        for (var j = 1; j < rows.length; ++j) {
            var cell = rows[j].cells[0];
            var rowValues = {};
            [ 'name', 'price' ].each(function(field) {
                var input = cssQueryFirst('input.option-'+field, cell);
                var value;
                if (input) {
                    value = input.value;
                } else {
                    value = cssQueryFirst('span.option-'+field, cell).firstChild.data;
                }
                rowValues[field] = value;
            });
            hash.rows.push(rowValues);
        }
    }
    return hash;
}

function onSubmitNewProduct(form) {
    cssQuery('input.autogenerated').each(Element.remove);
    var optionGroupIndex = 0;
    cssQuery('li', $('options')).each(function(optionGroup) {
        var matrixTable = cssQueryFirst('table.option-matrix', optionGroup);
        if (matrixTable) {
            var optionHash = buildOptionMatrixHashFromForm(optionGroup);
            // columns
            for (var i = 0; i < optionHash.columns.length; ++i) {
                addHiddenInput(form, 'option_columns['+optionGroupIndex.toString()+']['+i.toString()+']', optionHash.columns[i]);
            }
            // rows
            for (var j = 0; j < optionHash.rows.length; ++j) {
                for (var field in optionHash.rows[j]) {
                    addHiddenInput(form, 'option_rows['+optionGroupIndex.toString()+']['+j.toString()+']['+field+']', optionHash.rows[j][field]);
                }
            }
        } else {
            var optionHash = buildOptionHashFromForm(optionGroup);
            for (var field in optionHash) {
                addHiddenInput(form, 'options['+optionGroupIndex.toString()+']['+field+']', optionHash[field]);
            }
        }
        ++optionGroupIndex;
    });
    addHiddenInput(form, 'option_group_count', optionGroupIndex);
}

function remakeOptionMatrixPreview(table) {
    var li = getAncestorByTagName('li', table);
    // li.id is of form option_5
    var previewId = li.id+'-preview';
    var previewBox = $(previewId);
    if (previewBox) {
        previewBox.innerHTML = '';
    } else {
        previewBox = document.createElement('div');
        previewBox.id = previewId;
        firstRealChild(cssQueryFirst('.options')).appendChild(previewBox);
    }
    clonedTable = table.cloneNode(true);
    clonedTable.removeClassName('option-matrix');
    previewBox.appendChild(clonedTable);
}

function removeOptionGroup(formElement) {
    var removable = getAncestorByClassName('removable', formElement);
    var id = removable.id.replace(/[^0-9]/g, '');
    Element.remove($("option_"+id+"-preview"));
    Element.remove(removable);
}

function showSpecialInstructionsBox(link) {
    var form = Object.extend($(link).up('form'), FormSaver);
    var container = Selector.findChildElements(form, ['.special-instructions']).first();
    if (!container.visible()) {
        form.restoreElementsIfSaved('item_special_instructions');
        container.show();
    } else {
        container.hide();
        form.saveAndClearElements('item_special_instructions');
    }
}

var OptionMatrixEditor = {
    activeCell: null,
    
    editCell: function(cell) {
        if (!cell.firstChild.data) {
            // element is being edited
            return;
        }
        // another element is being edited, finish that
        if (this.activeCell) {
            this.finishEditingCell(this.activeCell);
        }
        // make a text input out of node data
        this.activeCell = cell;
        var value = cell.firstChild.data;
        cell.removeChild(cell.firstChild);
        var input = document.createElement('input');
        input.type = 'text';
        input.value = value;
        input.className = cell.className;
        input.onblur = OptionMatrixEditor.finishEditingCell;
        cell.appendChild(input);
        setTimeout(function() {
            input.select();
        }, 100);
    },
    
    // the following method may be called with 'this' being the input it's called on
    finishEditingCell: function() {
        // make a node data out of text input
        var cell = OptionMatrixEditor.activeCell;
        var input = cell.firstChild;
        var value = input.value;
        cell.removeChild(input);
        if (!value) {
            var row = getAncestorByTagName('tr', input);
            if (cssQueryFirst('input', row)) {
                value = 'Row';
            } else {
                value = 'Column';
            }
        }
        var text = document.createTextNode(value);
        cell.appendChild(text);
        // preview
        remakeOptionMatrixPreview(getAncestorByTagName('table', cell));
        OptionMatrixEditor.activeCell = null;
    }
};

function onAddOptionMatrix(displayOrder) {
    new Ajax.Updater(cssQueryFirst('.replacement-target', $('option_'+displayOrder)), '/products/new_option_matrix', {
        asynchronous: true, evalScripts: true, onComplete: function() {
            if (window.Behavior) {
                Behavior.apply();
            }
            remakeOptionMatrixPreview($('option_'+displayOrder).firstChild);
        }, parameters: 'table[rows]='+cssQueryFirst('#option_'+displayOrder+' .table-rows').value+
        '&table[columns]='+cssQueryFirst('#option_'+displayOrder+' .table-columns').value });
}

function collapseOption(link) {
    var li = getAncestorByTagName('li', link);
    var summary = cssQueryFirst('.collapsed-summary', li);
    var optionHash = buildOptionHashFromForm(li);
    summary.innerHTML = (optionHash.kind+": "+optionHash.description).escapeHTML();
    cssQueryFirst('.collapsed', li).show();
    cssQueryFirst('.expanded', li).hide();
}

function collapseOptionMatrix(link) {
    var li = getAncestorByTagName('li', link);
    if (cssQueryFirst('.option-matrix', li)) {
        var summary = cssQueryFirst('.collapsed-summary', li);
        var optionHash = buildOptionMatrixHashFromForm(li);
        summary.innerHTML = ("matrix: "+optionHash.rows.length.toString()+"x"+optionHash.columns.length.toString()+
            " "+optionHash.rows[0].name+" "+optionHash.columns[0]).escapeHTML();
    }
    cssQueryFirst('.collapsed', li).show();
    cssQueryFirst('.expanded', li).hide();
}

function expandOption(link) {
    var li = getAncestorByTagName('li', link);
    cssQueryFirst('.collapsed', li).hide();
    cssQueryFirst('.expanded', li).show();
}

function anchorPopup(popup, anchor, options) {
    options = options || {};
    anchor = $(anchor);
    popup = $(popup);
    var offset = (options.positioned ? anchor.positionedOffset() : anchor.cumulativeOffset());
    popup.style.left = offset.left+'px';
    popup.style.top = offset.top+anchor.offsetHeight+'px';
}

function closePopup() {
    $('popup').hide();
}

// cuisine/alphabet lists
var HoverBlock = Class.create({
    initialize: function(anchor, content) {
        this.anchorElement = $(anchor);
        this.contentElement = $(content);
        this.mouseOutHandler = this.onMouseOut.bindAsEventListener(this);
    },
    
    onMouseOut: function(event) {
        var pos = getMousePosition(event);
        if ([this.anchorElement, this.contentElement].all(function(elt) {
            var eltDims = elt.getDimensions();
            var eltPos = findPos(elt);
            return (pos[0] < eltPos[0] || pos[0] >= eltPos[0]+eltDims.width) ||
                (pos[1] < eltPos[1] || pos[1] >= eltPos[1]+eltDims.height);
        })) {
            this.close();
        }
    },
    
    open: function() {
        var anchorPos = this.anchorElement.positionedOffset();
        this.contentElement.setStyle({
            left: anchorPos.left+'px',
            top: anchorPos.top+this.anchorElement.getDimensions().height+'px'
        });
        this.contentElement.show();
        this.contentElement.observer = this;
        Event.observe(this.anchorElement, 'mouseout', this.mouseOutHandler);
        Event.observe(this.contentElement, 'mouseout', this.mouseOutHandler);
        return this;
    },
    
    close: function() {
        this.contentElement.hide();
        Event.stopObserving(this.anchorElement, 'mouseout', this.mouseOutHandler);
        Event.stopObserving(this.contentElement, 'mouseout', this.mouseOutHandler);
        this.contentElement.observer = undefined;
    },
    
    centerHorizontally: function() {
      var dims = this.contentElement.getDimensions();
      this.contentElement.style.marginLeft = -dims.width/2+'px';
      this.contentElement.style.left = '50%';
      
      var offset = this.contentElement.cumulativeOffset();
      var anchorDims = this.anchorElement.getDimensions();
      var anchorOffset = this.anchorElement.cumulativeOffset();
      if (offset.left < anchorOffset.left && offset.left + dims.width < anchorOffset.left) {
        this.contentElement.style.marginLeft = '0px';
        this.contentElement.style.left = '0px';
        var offset = this.contentElement.cumulativeOffset();
        this.contentElement.style.left = anchorOffset.left - offset.left + anchorDims.width - dims.width + 'px';
      }
    }
});

function moveData(anchor, data) {
    new HoverBlock(anchor, data || 'data').open().centerHorizontally();
    //$('data').show();
}

function closeList(data) {
    var data = $(data || 'data');
    if (data.observer) {
        data.observer.close();
    } else {
        data.hide();
    }
}
// end cuisine/alphabet lists

function jumpToStore(select) {
    var id = select.options[select.selectedIndex].value;
    location.href = '/stores/manage/'+id;
}

function rateRateable(type, id, rating, storeId) {
    new Ajax.Request('/ratings/rate/'+id+'?rateable_type='+type+'&rating='+rating+'&update=rating-'+id+'&store='+storeId, {asynchronous:true, evalScripts:true});
}

function expandInstructions(key, language, hash) {
    var container = $('instructions-'+hash+'-wrap');
    container.down('.collapsed').hide();
    container.down('.expanded').show();
    new Ajax.Request('/welcome/record_instruction_preference?key='+key+'&expanded=1&language='+language, {asynchronous:true, evalScripts:true});
}

function collapseInstructions(key, language, hash) {
    var container = $('instructions-'+hash+'-wrap');
    container.down('.expanded').hide();
    container.down('.collapsed').show();
    new Ajax.Request('/welcome/record_instruction_preference?key='+key+'&expanded=0&language='+language, {asynchronous:true, evalScripts:true});
}

function editInstructions(key, language, hash) {
    var container = $('instructions-'+hash+'-wrap');
    var box = container.down('.instructions-container');
    if (box) {
        box.hide();
    }
    container.down('.instructions-edit-container').show();
}

function finishEditingInstructions(hash) {
    var container = $('instructions-'+hash+'-wrap');
    container.down('.instructions-edit-container').hide();
    var box = container.down('.instructions-container');
    if (box) {
        box.show();
    }
}

function cancelEditingInstructions(hash) {
    finishEditingInstructions(hash);
}

function appendUrlParams(params, options) {
  var query = location.search.parseQuery();
  for (var key in params) {
    query[key] = params[key];
  }
  if (options && options.optional) {
    var relevantQuery = {};
    for (var key in query) {
      if (!query[key]) {
        skip = false;
        for (var i = 0; i < options.optional.length; ++i) {
          if (options.optional[i] == key) {
            skip = true;
            break;
          }
        }
        if (!skip) {
          relevantQuery[key] = query[key];
        }
      } else {
        relevantQuery[key] = query[key];
      }
    }
    query = relevantQuery;
  }
  query = Object.toQueryString(query);
  return location.pathname+'?'+query;
}

function redirectAddToProductPage(form, id) {
  var scroll = document.viewport.getScrollOffsets();
  var returnPath = appendUrlParams({scroll_x: scroll.left, scroll_y: scroll.top}, {optional: ['scroll_x', 'scroll_y']});
  var params = {return_path: returnPath};
  // note possible xss hole here if quantity is not escaped
  params['item[quantity]'] = $(form).down('.item-quantity').value;
  location.href = form.action + '?' + Object.toQueryString(params);
}

function validateDeliveryTime(form) {
  new Ajax.Request('/checkout/validate_delivery_time', {
    asynchronous: true, evalScripts: true, parameters: Form.serialize(form),
    onSuccess: function() {
      form.submit();
    }
  });
}

function updateDeliveryTime(form) {
  new Ajax.Request('/cart/update_delivery_time', {asynchronous: true, evalScripts: true, parameters: Form.serialize(form) });
}

var AlternatingTitle = Class.create({
  blink: function(titles) {
    this.titles = titles;
    this.stopTimer();
    this.setTitle(0);
    this.startTimer();
  },
  
  set: function(title) {
    this.stopTimer();
    document.title = title;
  },
  
  setTitle: function(index) {
    index = index % this.titles.length;
    document.title = this.titles[index];
  },
  
  onTimer: function(index) {
    index = (index || 0) + 1;
    this.setTitle(index);
    this.setTimer(index);
  },
  
  startTimer: function() {
    if (!this.timer) {
      this.setTimer();
    }
  },
  
  setTimer: function(index) {
    this.timer = setTimeout(this.onTimer.bind(this).curry(index), 1000);
  },
  
  stopTimer: function() {
    if (this.timer) {
      clearTimeout(this.timer);
      this.timer = null;
    }
  }
});

function postTo(url) {
  new Ajax.Request(url, {asynchronous: true, evalScripts: true});
}

function disableElementByEvent(event) {
  var element = Event.element(event);
  element.disabled = true;
}

Object.extend(Form.Methods, {
    validate: function(form, fn) {
        form.onsubmit = function() {
            form.errorMessages = [];
            var output = form.down('.errors');
            output.innerHTML = '';
            fn(form);
            if (form.errorMessages.length > 0) {
                form.errorMessages.each(function(message) {
                    var element = new Element('li');
                    element.appendChild(document.createTextNode(message));
                    output.appendChild(element.wrap('ul'));
                });
                return false;
            } else {
                return true;
            }
        };
    },
    
    error: function(form, message) {
        form.errorMessages.push(message);
    }
});

Element.addMethods();

Event.observeWithLoad = function(element, eventName, fn, event) {
    Event.observe(element, eventName, fn);
    var event = Event.fire(element, eventName);
    fn(event);
};

// this catches javascript syntax errors in rjs templates
Ajax.Responders.register({
    onException: function(request, exception) {
        alert(exception);
    }
});

document.observe('dom:loaded', function() {
    $$('table.want-last-row tr:last-of-type').each(function(element) {
        element.addClassName('last-row');
    });
    $$('ul.want-last li:last-of-type').each(function(element) {
        element.addClassName('last');
    });
    $$('.order-history-table').each(function(element) {
        stripeContainer(element, {oddClass: 'nch'});
    });
    $$('input.disable-on-click').each(function(element) {
      element.disabled = false;
      Event.observe(element, 'click', disableElementByEvent);
    });
    $$('input.enable-on-load').each(function(element) {
      element.disabled = false;
    });
    if (Popup.initialize) {
      Popup.initialize();
    }
});

document.observe('click', function() {
  $$('#points-popup').each(function(element) {
    element.remove();
  });
});
var Rating = {
  generate: function(namespace, type, id, storeId, rating) {
    var name = namespace;
    if (name) {
      name += '-';
    }
    name += 'rate-'+type+'-'+id;
    var container = $(name);
    var template = new Template($('rateable-template').value);
    var vars = {className: type, id: id, storeId: storeId, rating: rating}
    vars.width = rating*20;
    var content = template.evaluate(vars);
    container.innerHTML = content;
  }
};
var OrderConfirmationPopup = Class.create({
  initialize: function() {
    $('accept-link').observe('click', this.onAccept.bindAsEventHandler(this));
    $('reject-link').observe('click', this.onReject.bindAsEventHandler(this));
    $('cancel-link').observe('click', this.onCancel.bindAsEventHandler(this));
  },
  
  // invoked when user clicks change status link, shows form to accept or reject order
  show: function(orderId, anchor) {
    this.orderId = orderId;
    $('time-for-delivery').value = '';
    $('accept-id').value = orderId;
    $('reject-id').value = orderId;
    $('accept-form').hide();
    $('reject-form').hide();
    $('reject-reason').value = $('default_reject_reason').value;
    var popup = $('order-confirmation-popup');
    Popup.showCenteredFixedSized(popup, {width: 300, height: 300, clickAction: 'none'});
  },
  
  // invoked when user chooses to accept order, shows order acceptance form
  onAccept: function() {
    $('accept-form').show();
    $('reject-form').hide();
  },
  
  // invoked when user chooses to reject order, shows order rejection form
  onReject: function() {
    $('reject-form').show();
    $('accept-form').hide();
  },
  
  // invoked when user chooses to mark order canceled, sends cancellation to server side without further prompting
  onCancel: function() {
    new Ajax.Request('/order_management/cancel/'+this.orderId, {asynchronous:true, evalScripts:true});
  },
  
  // invoked when status has been updated on the server side, hides the popup
  onDone: function(newStatus) {
    $('order-confirmation-popup').hide();
    $('status-'+$('accept-id').value).innerHTML = newStatus;
  }
});

OrderConfirmationPopup.initialize = function() {
  window.OrderConfirmationPopup = new window.OrderConfirmationPopup;
};
var HomepageStoresSearchForm = Class.create({
  initialize: function() {
    this.kind = $('kind').value;
  },
  
  showAddressSearchForm: function() {
    $$('.search-by-subway').each(Element.hide);
    $$('.search-by-address').each(Element.show);
    $('search_by').value = 'address';
    $('search-by-subway-span').className = 'search-link';
    $('search-by-address-span').className = 'search-link-active';
  },
  
  showSubwaySearchForm: function() {
    $$('.search-by-address').each(Element.hide);
    $$('.search-by-subway').each(Element.show);
    $('search_by').value = 'subway';
    $('search-by-subway-span').className = 'search-link-active';
    $('search-by-address-span').className = 'search-link';
  },
  
  showSearchFormWithoutSuburbs: function() {
    $$('.search-with-suburbs').each(Element.hide);
    $$('.search-without-suburbs').each(Element.show);
    HomepageStoresSearchForm.showAddressSearchForm();
  },
  
  showSearchFormWithSuburbs: function() {
    $$('.search-without-suburbs').each(Element.hide);
    $$('.search-with-suburbs').each(Element.show);
    $('search_by').value = 'suburb';
  },
  
  submitSearchForm: function(form) {
    var fields = 'kt';
    var values = [this.kind, form['wsp[location][city_id]'].value];
    var value;
    if (form['wsp[search_by]'].value == 'address') {
      fields += 'a';
      if (value = form['wsp[location][street]'].value) {
        values.push('+'+encodeURIComponent(value));
      } else {
        values.push('-');
      }
    } else if (form['wsp[search_by]'].value == 'subway') {
      fields += 's';
      if (value = form['wsp[location][subway_id]'].value) {
        values.push(value);
      } else {
        values.push('-');
      }
    } else {
      fields += 'b';
      if (value = form['wsp[location][suburb_id]'].value) {
        values.push(value);
      } else {
        values.push('-');
      }
    }
    var url = '/search/'+fields+'/'+values.join('/');
    if (this.kind == 2 || this.kind == 3) {
      url += '?wsp[delivery_time][date]=' + encodeURIComponent($('wsp_delivery_time_date').value);
      url += '&wsp[delivery_time][time]=' + encodeURIComponent($('wsp_delivery_time_time').value);
    }
    window.location = url;
  }
});

HomepageStoresSearchForm.initialize = function() {
  window.HomepageStoresSearchForm = new HomepageStoresSearchForm;
};
var StoreMapPopup = Class.create({
  show: function(lat, lng, anchor) {
    var container = $('map-container');
    anchor = $(anchor).down('img') || $(anchor);
    var pos = anchor.cumulativeOffset();
    var top = pos.top-420;
    top = top < 0 ? 0 : top;
    container.style.top = top+'px';
    var left = pos.left-710+anchor.offsetWidth;
    left = left < 0 ? 0 : left;
    container.style.left = left+'px';
    container.show();
    var map = new GMap2($('map'));
    map.addControl(new GLargeMapControl());
    map.addControl(new GMapTypeControl());
    var center = new GLatLng(lat, lng);
    map.setCenter(center, 13);
    map.addOverlay(new GMarker(center));
  },
  
  hide: function() {
    $('map-container').hide();
  }
});

StoreMapPopup.initialize = function() {
  window.StoreMapPopup = new window.StoreMapPopup;
};
var HomepageStoresPage = {
  showLargeStoreBlock: function(storeId) {
    Element.hide("small-"+storeId);
    $$("#small-"+storeId+" .rating").each(function(element) { element.id = ''; });
    Element.show("large-"+storeId);
    $$("#large-"+storeId+" .rating").each(function(element) { element.id = 'rating-'+storeId; });
    if (Prototype.Browser.IE) {
      $$('.rating').each(function(element) { element.hide(); element.show(); });
    }
  },
  
  showSmallStoreBlock: function(storeId) {
    Element.hide("large-"+storeId);
    $$("#large-"+storeId+" .rating").each(function(element) { element.id = ''; });
    Element.show("small-"+storeId);
    $$("#small-"+storeId+" .rating").each(function(element) { element.id = 'rating-'+storeId; });
    if (Prototype.Browser.IE) {
      $$('.rating').each(function(element) { element.hide(); element.show(); });
    }
  }
};
var Popup = Class.create({
  showCenteredFixedSized: function(popup, options) {
    options = options || {};
    options.clickAction = options.clickAction || 'remove';
    popup = $(popup);
    var offsets = document.viewport.getScrollOffsets();
    var dims = document.viewport.getDimensions();
    popup.style.left = offsets.left+(dims.width-options.width)/2+'px';
    popup.style.top = offsets.top+(dims.height-options.height-60)/2+'px';
    if (options.clickAction != 'none') {
      popup.observe('click', function() {
        popup[options.clickAction]();
      });
    }
    popup.show();
  },
  
  showCenteredSelfSized: function(popup, options) {
    options = options || {};
    popup = $(popup);
    var dimensions = popup.getDimensions();
    options.width = dimensions.width;
    options.height = dimensions.height;
    this.showCenteredFixedSized(popup, options);
  },
  
  showCenteredImageSized: function(popup, image, options) {
    options = options || {};
    options.width = image.width;
    options.height = image.height;
    this.showCenteredFixedSized(popup, options);
  }
});

Popup.initialize = function() {
  window.Popup = new Popup();
};

var TimedPopup = Class.create({
  initialize: function(event, element, options) {
    this.options = options || {};
    this.element = $(element);
    if (this.element.timedPopup) {
      this.element.timedPopup.kill();
    }
    if (options.exclusive && window.timedPopups) {
      while (window.timedPopups.length > 0) {
        window.timedPopups[0].kill(true);
      }
    }
    if (this.options.popupElementFunction) {
      this.popup = this.options.popupElementFunction(this.element);
    } else {
      this.popup = this.element.down('.popup');
    }
    var positionedOffset = this.element.positionedOffset();
    var cumulativeOffset = this.element.cumulativeOffset();
    var left = event.pageX-cumulativeOffset.left+positionedOffset.left;
    var top = event.pageY-cumulativeOffset.top+positionedOffset.top;
    this.popup.style.left = left+'px';
    this.popup.style.top = top+'px';
    this.element.timedPopup = this;
    this.element.onmouseout = this.onMouseOut.bindAsEventListener(this);
    this.popup.onmouseover = this.onPopupMouseOver.bindAsEventListener(this);
    this.popup.onmouseout = this.onMouseOut.bindAsEventListener(this);
    if (window.timedPopups == undefined) {
      window.timedPopups = [];
    }
    var zIndex = 0;
    for (var i = 0; i < window.timedPopups.length; ++i) {
      zIndex = window.timedPopups[i].zIndex;
    }
    if (zIndex == 0) {
      zIndex = 4000;
    }
    this.popup.style.zIndex = this.zIndex = zIndex + 1;
    window.timedPopups.push(this);
    this.popup.show();
  },
  
  onMouseOver: function() {
  },
  
  onMouseOut: function() {
    this.destructionTimer = setTimeout(this.onTimeout.bindAsEventListener(this), this.options.timeout || 1000);
  },
  
  onPopupMouseOver: function() {
    this.stopTimer();
  },
  
  onTimeout: function() {
    this.popup.hide();
    this.cleanup();
  },
  
  kill: function(hide) {
    this.stopTimer();
    if (hide) {
      this.popup.hide();
    }
    this.cleanup();
  },
  
  stopTimer: function() {
    if (this.destructionTimer) {
      clearTimeout(this.destructionTimer);
      this.destructionTimer = undefined;
    }
  },
  
  cleanup: function() {
    this.element.timedPopup = undefined;
    this.element = undefined;
    this.popup = undefined;
    for (var i = 0; i < window.timedPopups.length; ++i) {
      if (window.timedPopups[i] == this) {
        window.timedPopups.splice(i, 1);
        break;
      }
    }
  }
});

TimedPopup.setup = function(element, options) {
  element.observe('mouseover', function(event) {
    var element = $(event.element());
    new TimedPopup(event, element, options);
  });
};
