Airshow 416

I posted a link to a fiddle back in the original post

Luke_4 commented: Airshow!!! Thank YOU!!! It is PERFECT!!! +0

Airshow 416

diafol commented: Lovely :) +15

Airshow 416

An approach (not the only one) would be to have two main child divs within your <body></body> tags

  • <div id="screen"></div> styled to be displayed under @media screen and hidden under @media print.
  • <div id="print"></div> styled to be displayed under @media print and hidden under @media screen.

When the "Print" button is clicked, dynamically populate the "print" div with content drawn from the "screeen" div, before calling window.print().

I have never needed to do this but it may be the line-of-least-resistance here. I can see no reason why it could not be made to work.

Luke_4 commented: Would like to have you elaborate with example +0

Airshow 416

@Luke_4,

It's very odd that printing is not WYSIWYG.

Theory 1: (Physical) .autocomplete() creates the widget by replacing the original input element and/or creating one or more hidden elements. You may need to find some element other than the obvious one, which holds the final string, and establish an @media print rule that shows only that element. Use your browser's console to inpect the DOM.

Theory 2: (Temporal) It's a matter of timing. You may need to issue some command on the widget and/or wait for some widget event before printing. Have a look at autocomplete's API documentation for clues.

Airshow 416

You are expecting javascript arrays to behave like PHP arrays. They don't!

Elements of a JS array are indexed exclusively by integer.

Confusingly, JS arrays can also be given properties, so tmp[14]['G'] = foo is quite legal but not what you might expect from knowledge of PHP. ['G'] is a property of tmp[14] without being an array element!

This all stems from the way JS implements arrays. Array is heavily based on Object but gives special status only to integer keys.

Straightforwardly you could do as follows :

// change
tmp[i] = [];
// to
tmp[i] = {}; // a js plain object

// change
tmp[i]['"'+letters[j]+'"'] = val;
// to
tmp[i][letters[j]] = val;

However, JS plain objects are just collections of properties ... without order ..., which may suffice.

But if order is important, and you want to convey both a key (letter) and a value (val), then you need an array of objects, each containing a key|value pair.

// keep
tmp[i] = []; // arrays have order.

// change
tmp[i]['"'+letters[j]+'"'] = val;
// to
tmp[i].push({ 'key':letters[j], 'value':val });

Love it or loath it, that's javascript.

diafol commented: Masterclass :) +15

Airshow 416

The formula will simplify enormously.

((lcost1*24-(dp1/2))/24)+((epay1-(dp1/2))/24)

can be written :

lcost1 + (epay1 - dp1) / 24

And you probably want to display :

(lcost1 + (epay1 - dp1) / 24).toFixed(2)

ie rounded to 2 decimal places.

Airshow 416

Gentlemedia, coooooool! Now Taywin and I can start breathing again ;-)

Airshow 416

Gentlemedia, it's not obvious what is causing this issue. You need to devise an investigation strategy to discover what's causing it. Simplify everything one aspect at a time and see when the problem disappears.

Airshow 416

Text questions will most likely attract text answers. If you want code, then provide code.

Airshow 416

Two things strike me immediately :

Click handlers

Take a look at the following :

$(".conjugate").click(function() {
    conjugate();
});

and

$(document).on("click", ".conjugate", function() {
    conjugate();
});

This means that any ".conjugate" element that is present in the document at page load will fire conjugate() twice when clicked!!!

jQuery selectors

$(selector) is an expensive operation, and the code abounds with them.

Consider finding all the static elements once and caching the resulting jQuery objects.

eg :

//cache of jQuery objects
var $$ = {
    verb: $("#verb"),
    if_add: $("#if_add"),
    verb_form: $("#verb_form"),
    add_info: $("#add_info"),
    status : $("#status"),
    conjugation : $("#conjugation"),
    irregular: $("#irregular")
};

Then use $$.verb, $$.if_add etc. inside conjugate().

diafol commented: First time i've seen the cache methood - big hand Airshow - thanks for the tip! +15

Airshow 416

Siberian, read the first line of my signature.

diafol commented: Be careful, he's a little sensitive! heh +15

Airshow 416

The problem is that count = $(".count") selects all the elements with class="count", not just the one you are interested in whenever a plus/minus button is clicked.

You need for the click handler to sniff out the particular .count element that is a sibling of the particular plus/minus button that was clicked.

Something like this should do it :

$('.minus').on('click', function() {
    var count = $(this).siblings(".count");
    var val = parseInt(count.val());
    if (val > 1)
        count.val(val - 1);
});
$('.plus').on('click', function() {
    var count = $(this).siblings(".count");
    var val = parseInt(count.val());
    count.val(val + 1);
});

Even better, you can delegate to the containing table element, AND handle both plus and minus with a single handler, as follows :

//Assuming the cart has id="cartTable"
$("#cartTable").on('click', '.minus, .plus', function() {
    var $this = $(this),
        $count = $this.siblings(".count"),
        delta = $this.hasClass('plus') ? 1 : -1; 
    $count.val(Math.max(0, parseInt($count.val()) + delta));
});

Not only is that more concice, but it will automatically handle :

  • any items already in the cart when this handler is put in place
  • any items added to the cart later (ie there's no need to attach individual click handlers to new items after they are added).

So, no worries if your cart is dynamically updated.

If it's not dynamically updated, then still no worries, delegation is a good idea anyway.

Airshow 416

Or, simplest of all :

if( typeof document.frmEntry.optMethod ) {
    ...
}
AleMonteiro commented: Didn't know this one ^^ +0

Airshow 416

If AleMonteiro's answer works, then so should this simplification of it :

var checkboxList = $("fieldset.step input:checked").map(function() {
    return {
        'step': $(this).closest("fieldset").index(),
        'checkboxId': this.id
    };
}).get();
AleMonteiro commented: Nice to know =) +8

Airshow 416

Waqas, that's a server-side problem with a different solution and best addressed in the PHP section, not JavaScript / DHTML / AJAX.

Airshow 416

Iamthwee, namespacing your code is something we should all consider in order to keep our javascript tidy. It's not specifically to do with runtime environemental issues. In other words it's something we might do even in the absence of a DOM - say we were writing some node.js for example.

And yes, "defining a scope" is a good way to think of it. Strictly speaking javascript only has global scope and functional scope; but we also have javascript plain objects and constructed objects (created with the new operator). Together these provide ways in which we can define private and public members, and avoid name collisions; all very desirable in complex code, to insulate the "module" we are currently working on from interference by other code (eg libs, or code written previously by yourself or another programmer).

Imagine what chaos might ensue if all members were in the global namespace. Think how careful you would have to be when using a 3rd party lib not to overwrite some critical aspect of that lib!

iamthwee commented: Thank you +14

Airshow 416

Sorry, that's javascript!

try $i = array_push($arr, array()) - 1;.

Airshow 416

That is what jquery's .one() is for.

$("#modal-launcher-user").one('click', function() {
    ...
});

Airshow 416

... then, to answer the actual question ...

When _postcode is blank, you can avoid relying on the Google geocode call to tell you whast you already know - ie there will be no results.

This can be achieved by modifying fetch_1 as follows :

function fetch_1(_postcode) {
    if(_postcode) {
        return $.ajax({
            url: 'http://maps.googleapis.com/maps/api/geocode/json',
            data: {
                address: encodeURIComponent(_postcode) + ',UK',
                sensor: 'false'
            }
        });
    }
    else {
        return $.Deferred().resolve(null).promise();
    }
}

fetch_2(), as already written, will detect the null and cause the Stores to be listed alphabetically.

Airshow 416

First, simplify the main function by pulling out the two ajax calls into their own functions as follows:

function fetch_1(_postcode) {
    return $.ajax({
        url: 'http://maps.googleapis.com/maps/api/geocode/json',
        data: {
            address: encodeURIComponent(_postcode) + ',UK',
            sensor: 'false'
        }
    });
}
function fetch_2(location) {
    location = location || {lat:0, lng:0};//null geo-location, to be handled server-side as a special case.
    return $.ajax({
        url: ajax_url,
        data: {
            lat: location.lat,
            lng: location.lng
        }
    }).done(function(data) {
        //display stores data here
        display_stores(data);//for example
    }).fail(function(jqXHR, textStatus, errorThrown) {
        //handle failure case here
        clear_stores();//for example
    }).always(function() {
        changeReadyState(true);
    });
}

Note how in fetch_2(), a null or undefined location defaults to {lat:0, lng:0}, which then needs to be handled as a special case server-side (ie geo sort not required). fetch_2() also has its own .done and .fail handlers, to avoid repetition inside generateResults().

Now generateResults() can be simplified to call fetch_1() and fetch_2(), and modified such that the parameter passed to fetch_2() is either a taken from a successful fetch_1() or null in the various cases where no postcode geolocation is available.

function generateResults(_postcode) {
    //console.log("generating results");
    if (readyState) {
        changeReadyState(false); // Changes the state of the application, lays a translucent DIV on top of the controls and only allows single execution of this function
        fetch_1(_postcode).then(function(d) {
            var loc = null;
            if (d.results && d.results[0] && d.results[0].geometry && d.results[0].geometry.location) {
                loc = d.results[0].geometry.location);
            }
            else if (d.error_message) {
                alert(d.error_message);
            }
            else {
                alert("No results found for '" + _postcode + "'");
                postcodeTxt.val("");
            }
            fetch_2(loc);
        }, function(jqXHR, textStatus, errorThrown) {
            fetch_2(null);
        });
    } ...

Airshow 416

Julia,

First give your set wrappers the class name "set" :

<div class="set" id="SET1">
    ...
</div>
<div class="set" id="SET2">
    ....
</div>

Then use the following glorious seven-method one line jQuery event handler :

$(".tiptext").on('click', function () {
    $(this).closest(".set").find(".description").hide().end().end().children(".description").show();
});
Julia25 commented: Genius! +2

Airshow 416

Are you saying the code doesn't work, or that it works but you don't understand why?

Airshow 416

Mossa,

Well done. I understand the problem better now you have explained the solution!

Never mind, let's hope we have better luck next time.

Best wishes - also to Diafol (he of the Virtual Hosts).

Airshow

diafol commented: Long time no write Airshow :) +14

Airshow 416

Patk, there's not enough information in the question to be able to answer it.

In order to help you, we would need to see at least the HTML on which the filters act. Once that is known, there would be two possible approaches :

  1. AJAX: Fetch data from which a new list of items could be built, replacing the current list
  2. non-AJAX: Filter the page's existing list of items directly without fetching any new data.

Both approaches are very different from reloading the entire page.

The choice of one approach or the other will be determined by whether or not you can initially compose an enduring, comprehensive list of items when the page is initially served.

If viable, the non-AJAX approach will be very fast.

The AJAX approach may not be any faster than your current technique (reloading the entire page) and would require a corresponding server-side resource to deliver the necessary data.

Airshow 416

Hi Mossa,

I have modified update() to address discounts as you defined them. You will need to :

  • modify the row template to add the two discount elements (class="discountVal" and class="discountBasis").
  • (if appropriate) modify the row template to include a class="discount" element to display the calculated discount.
  • (if appropriate) similarly modify updatelabor().

Here's update() :

function update() {
    var $this, $row, rowUnitPrice, rowQty, rowRawPrice, rowTax, rowIsTaxable, rowPrice,
        rowDiscountValue, rowDiscountBasis, rowDiscount,
        totals = {taxable: 0, taxExempt: 0, tax: 0 };
    $('.price', $item_rows_container).each(function() {
        $this = $(this);
        $row = $this.closest('tr');
        rowUnitPrice = $row.find('.costbox').val().fromCurrency() || 0;
        rowQty = Number($row.find('.qtybox').val()) || 0;
        rowRawPrice = (rowUnitPrice * rowQty).toCurrency().fromCurrency();

        // *** start discount calcs ***
        //assume each row's two discount input elements have 
        //   class="discountVal" (a number)
        //   class="discountBasis" (% or $)
        rowDiscountValue = Number($row.find('.discountVal').val());
        rowDiscountBasis = $row.find('.discountBasis').val();
        if(rowDiscountBasis == '%') {
            rowDiscountValue = Math.max(Math.min(rowDiscountValue, 100), 0);//apply sensible limits
            rowDiscount = rowRawPrice * rowDiscountValue / 100;
        }
        else {
            rowDiscountValue = Math.max(Math.min(rowDiscountValue, rowRawPrice), 0);//apply sensible limits
            rowDiscount = rowDiscountValue;
        }
        rowDiscount = rowDiscount.toCurrency().fromCurrency() || 0;
        rowDiscountedPrice = rowRawPrice - rowDiscount;
        // *** end discount calcs ***

        rowTax = (rowDiscountedPrice * taxRate).toCurrency().fromCurrency();
        rowIsTaxable = $row.find('.taxable').is(':checked');

        //In this block, rowRawPrice is now replaced by rowDiscountedPrice.
        if(rowIsTaxable) {
            rowPrice = rowDiscountedPrice + rowTax;
            totals.taxable += rowDiscountedPrice;
            totals.tax += rowTax;
        }
        else {
            rowPrice = rowDiscountedPrice;
            totals.taxExempt += rowDiscountedPrice;
        }

        // *** start discount presentation ***
        //if you want to display the row's discount (especially useful for percentage).
        $row.find(".discount").html(isNaN(rowDiscount) ? errorMsg : rowDiscount.toCurrency());
        // *** end discount ...
mbarandao commented: Great Stuff Airshow, Thanks! +2

Airshow 416

If moreFields() is modified to work with arguments, then you can't simply attach the function as the onload handler. You have to create an event handler from which the function is called in the right way.

For example :

window.onload = function(event) {
    moreFields('water_src', 'water_drop');
}

Airshow 416

I guess it's a question of getting the regexp correct, then knowing what to do with it.

Something like this maybe :

$("#nickname").keyup(function(event) {
    var validate = /^(_|[A-Za-z0-9])*$/;
    var nickname = $(this).val();
    var ok = validate.test(nickname);
    $("#message-area").html(ok ? 'ok' : 'not OK');
});
Martin C++ commented: Thanks for help, worked perfectly +2

Airshow 416

Glennt, you appear to describe something known as "session crosstalk".

It's not a subject I know much about but Google gives several results for "ASP.net session crosstalk", indicating that it's not an unknown issue.

Airshow 416

OP, you are in "JavaScript / DHTML / AJAX" and javascript doesn't have associative arrays.

Airshow 416

Szabizs,

Ids are designed to be unique whilst classes are designed to be multiple.

With an expression such as $("#myId"), jQuery will select only the first matching element.

With an expression such as $(".myClass"), jQuery will select all matching elements.

This is a feature of the DOM and javascript, to which jQuery has no option but to conform.