Setting the screen position when visiting previous page with jQuery
The scenario
This problem came while I was working on a client’s website. Take this scenario. I have a listing page (similar to a listing page from www.trademe.co.nz or www.ascent.co.nz ). They have a lot of listings. Each listing has a hyperlink going to a detail page which describes more about each listing.
If you have quite a large page of listings, the user has to scroll down the page to find the listing they want to click on. The problem is, when they click on the link and go to the details page, what happens when the click back and they end up back at the top of the page again? This can be quite annoying. It would be “nice” to get the browser to go back to the previous position on the listing page.
The Approach
We achieve this but attach a click event handler to the specific a tags that we want to record the last clicked position from the user before we redirect to the next page. This means when the user goes back to the previous page they will be taken to the exact position on the page. When the click event happens we always override what was in the cookie so that we don’t end up with several pages and positions. We don’t want a user to go to another page they have been to before and set the scroll position, it should only happen to the previous page.
Here is the code
$(document).ready(function () {
// Check to see if the user already has the cookie set to scroll
var scrollPosition = getCookiePosition(COOKIE_NAME);
if (scrollPosition.length > 0) {
// Scroll to the position of the last link clicked
window.scrollTo(0, parseInt(scrollPosition, 10));
}
// Attach an overriding click event for each link
// saveScrollPosition function can be called before the user is redirected.
$("a.savel").each(function () {
$(this).click(function () {
saveScrollPosition($(this));
});
});
});
// Get the offset (height from top of page and the current page url) of the link element
// and save it in a cookie.
function saveScrollPosition(link) {
var linkTop = link.offset().top;
setCookie(COOKIE_NAME, "pos=" + linkTop + "&link=" + escape(window.location.pathname), 1);
}
//Trim whitespaces
function trim(str) {
return str.replace(/^\s+|\s+$/g, '');
}
// Get cookie helper function
function getCookiePosition(name) {
if (document.cookie.length > 0) {
//Get the right cookie
var cookies = document.cookie.split(";");
for (var i = 0; i < cookies.length; i++) {
if (trim(cookies[i].split("=")[0]) == COOKIE_NAME) {
//Get the position of the & sign
var pos = cookies[i].indexOf("&");
if (pos > -1) {
//Get value
var scrollValue = cookies[i].split("=")[2].substring(0, cookies[i].split("=")[2].indexOf("&"));
var link = unescape(trim(cookies[i].split("=")[3]));
//if the link is the same as the current page, then assume user wants to go back to the same position
if (link == window.location.pathname) return scrollValue;
}
return "";
}
}
return "";
}
return "";
}
//Method to create a new cookie
function createCookie(name, value, days) {
if (days) {
var date = new Date();
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
var expires = "; expires=" + date.toGMTString();
}
else var expires = "";
document.cookie = name + "=" + value + expires + "; path=/";
}
// Set cookie
function setCookie(name, value, expiredays) {
//Remove all other cookies
var cookies = document.cookie.split(";");
for (var i = 0; i < cookies.length; i++) {
if (trim(cookies[i].split("=")[0]) == COOKIE_NAME) {
createCookie(trim(cookies[i].split("=")[0]), "", -1);
}
}
//Set the new cookie
createCookie(name, value, expiredays);
}
Magento Fontis Paymate Module Fix for Not Passing Data Issue
I upgraded my magento from 1.4.0.1 to 1.6.1 and yes a lot of things broke especially the ability to pay using Paymate.
I did some research and found a lot of people having the issue of non data being passed from Magento to Paymate to populate the fields like address, amount etc. Since there has been no support at all? I decided to take matters into my own hands.
Here is the code from the file: /app/code/community/Fontis/Paymate/Model/Paymate.php
replace this file with your original file Paymate.php. As always I suggest that you back up everything!
This worked for me. Technically the issue was the code didn’t like getting the getQuote() from the checkout/session object. Instead getting the getLastRealOrderId from checkout/session and loading a new model with the order id worked.
/**
* Fontis Paymate Extension
*
* NOTICE OF LICENSE
*
* This source file is subject to the Open Software License (OSL 3.0)
* that is bundled with this package in the file LICENSE.txt.
* It is also available through the world-wide-web at this URL:
* http://opensource.org/licenses/osl-3.0.php
* If you did not receive a copy of the license and are unable to
* obtain it through the world-wide-web, please send an email
* to license@magentocommerce.com so one can be sent to you a copy immediately.
*
* @category Fontis
* @package Fontis_Paymate
* @author Lloyd Hazlett
* @author Chris Norton
* @copyright Copyright (c) 2010 Fontis Pty. Ltd. (http://www.fontis.com.au)
* @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0)
*/
/**
* Paymate payment model
*
* @category Fontis
* @package Fontis_Australia
*/
class Fontis_Paymate_Model_Paymate extends Mage_Payment_Model_Method_Abstract
{
const CGI_URL = 'https://www.paymate.com/PayMate/ExpressPayment';
const CGI_URL_TEST = 'https://www.paymate.com/PayMate/TestExpressPayment';
const REQUEST_AMOUNT_EDITABLE = 'N';
protected $_code = 'paymate';
protected $_formBlockType = 'fontis_paymate_block_form';
protected $_allowCurrencyCode = array('AUD', 'EUR', 'GBP', 'NZD', 'USD');
protected $_isGateway = false;
protected $_canAuthorize = false;
protected $_canCapture = true;
protected $_canCapturePartial = false;
protected $_canRefund = false;
protected $_canVoid = false;
protected $_canUseInternal = false;
protected $_canUseCheckout = true;
protected $_canUseForMultishipping = false;
/**
* Assign data to info model instance
*
* @param mixed $data
* @return Fontis_Australia_Model_Payment_Paymate
*/
public function assignData($data)
{
$details = array();
if ($this->getUsername())
{
$details['username'] = $this->getUsername();
}
if (!empty($details))
{
$this->getInfoInstance()->setAdditionalData(serialize($details));
}
return $this;
}
public function getUsername()
{
return $this->getConfigData('username');
}
public function getUrl()
{
$url = $this->getConfigData('cgi_url');
if(!$url)
{
$url = self::CGI_URL_TEST;
}
return $url;
}
/**
* Get session namespace
*
* @return Fontis_Australia_Model_Payment_Paymate_Session
*/
public function getSession()
{
return Mage::getSingleton('paymate/paymate_session');
}
/**
* Get checkout session namespace
*
* @return Mage_Checkout_Model_Session
*/
public function getCheckout()
{
return Mage::getSingleton('checkout/session');
}
/**
* Get current quote
*
* @return Mage_Sales_Model_Quote
*/
public function getQuote()
{
return $this->getCheckout()->getQuote();
}
public function getCheckoutFormFields()
{
$orderIncrementId = $this->getCheckout()->getLastRealOrderId();
$order = Mage::getModel('sales/order')->loadByIncrementId($orderIncrementId);
$a = $order->getShippingAddress();
$b = $order->getBillingAddress();
$currency_code = $order->getCurrencyCode();
//$cost = $order->getSubtotal() - $order->getDiscountAmount();
$cost = $order->getGrandTotal();
//$shipping = $order->getShippingAmount();
//$_shippingTax = $order->getShippingAddress()->getTaxAmount();
//$_billingTax = $order->getBillingAddress()->getTaxAmount();
//$tax = sprintf('%.2f', $_shippingTax + $_billingTax);
//$cost = sprintf('%.2f', $cost + $tax);
$fields = array(
'mid' => $this->getUsername(),
'amt' => sprintf('%.2f', $cost + $shipping),
'amt_editable' => self::REQUEST_AMOUNT_EDITABLE,
'currency' => $currency_code,
'ref' => $orderIncrementId,
'pmt_sender_email' => $b->getEmail(),
'pmt_contact_firstname' => $b->getFirstname(),
'pmt_contact_surname' => $b->getLastname(),
'pmt_contact_phone' => $b->getTelephone(),
'pmt_country' => $b->getCountry(),
'regindi_address1' => $b->getStreet(1),
'regindi_address2' => $b->getStreet(2),
'regindi_sub' => $b->getCity(),
'regindi_state' => $b->getRegion(), // Returns full state name
'regindi_pcode' => $b->getPostcode(),
'return' => Mage::getUrl('paymate/paymate/complete'),
'popup' => 'N',
);
// Run through fields and replace any occurrences of & with the word
// 'and', as having an ampersand present will conflict with the HTTP
// request.
$filtered_fields = array();
foreach ($fields as $k=>$v) {
$value = str_replace("&","and",$v);
$filtered_fields[$k] = $value;
}
return $filtered_fields;
}
public function createFormBlock($name)
{
$block = $this->getLayout()->createBlock('paymate/paymate_form', $name)
->setMethod('paymate')
->setPayment($this->getPayment())
->setTemplate('fontis/paymate/form.phtml');
return $block;
}
/*validate the currency code is avaialable to use for paypal or not*/
public function validate()
{
parent::validate();
$currency_code = $this->getQuote()->getBaseCurrencyCode();
if (!in_array($currency_code,$this->_allowCurrencyCode)) {
Mage::throwException(Mage::helper('paymate')->__('Selected currency code ('.$currency_code.') is not compatabile with Paymate'));
}
return $this;
}
public function onOrderValidate(Mage_Sales_Model_Order_Payment $payment)
{
return $this;
}
public function onInvoiceCreate(Mage_Sales_Model_Invoice_Payment $payment)
{
}
public function canCapture()
{
return true;
}
public function getOrderPlaceRedirectUrl()
{
return Mage::getUrl('paymate/paymate/redirect');
}
}
When to use TempData in ASP.NET MVC
The problem
I came across this problem where I had a basic list of items and for each if these items I wanted to add a delete action on my controller. I wanted to add some error checking around the delete action so if someone attempted to modify the URL and enter a incorrect parameter into my controller, I wanted to redirect back to the previous controller and notify the user of this mistake. Take this example. I have a Product Controller and the Index Action and View lists all of the products. I have a delete action on the Product Controller which checks if the productId parameter is valid and then removes the product and redirects to the Index action using the RedirectToAction method.
The solution
When validating the productId, if its NOT valid simply add a new key/value to the TempData object and call the RedirectToAction method. TempData stores data for short periods of time for the current and next HTTP request. In your index view, you can check to see if your key/value pair exists and display a error message. If you refresh the page, you will notice the data from the TempData will be gone. Remember to use RedirectToAction where possible as this is more friendly with Unit Testing rather than using the HttpResponse redirect method.
This reference illustrates a good understanding between TempData, ViewBag and ViewData.
http://rachelappel.com/when-to-use-viewbag-viewdata-or-tempdata-in-asp.net-mvc-3-applications
Perform ASP.NET Postback using JQuery
Came across a situation where I needed to disable the ASP.NET button using JQuery when the user clicks on the button, this is so we stop the user from clicking the button twice while the page is doing a postback.
Firstly, add the following JQuery/Javascript code:
function autoSubmit()
{
<%= ClientScript.GetPostBackEventReference(btnSaveChanges, "") %>;
}
$(function () {
$("#<%= btnSaveChanges.ClientID %>").click(function () {
$(this).attr("disabled", "true");
autoSubmit();
return false;
});
});
</script>
You can inject the correct generated javascript code from using the function GetPostBackEventReference between the server tags. Bind a click event to the ASP.NET server control which disables the button and call the autosubmit javascript function to perform a post back on the ASP.NET button control.
References
Some useful Extension Methods
Back to basics…
A couple of useful Extension methods if you are checking if a string is a valid integer or decimal.
{
if (String.IsNullOrEmpty(value)) return false;
Int32 tmpNo;
return Int32.TryParse(value, out tmpNo);
}
public static bool IsDecimal(this string value)
{
if (String.IsNullOrEmpty(value)) return false;
Decimal tmpNo;
return Decimal.TryParse(value, out tmpNo);
}
To implement the Extension Methods:
if (myString.IsDecimal())
{
//Do something
}
Areas and ASP.NET MVC Routes Tip
Lets say that you have several areas define in your ASP.NET MVC solution. You find that when you run your application you get the following error “Multiple types were found that match the controller named ‘Home’.”. This is because ASP.NET MVC finds all the routing definitions (i.e in each area.cs file or the global.asax) and sees conflicts with duplicate controller names.
Simple solution is to associate the namespace with the routes you are registering. Take the example below:
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = UrlParameter.Optional }, // Parameter defaults
new string[] { "MyApplication.Controllers" } // Controller Namespace
);
?
// Area Registration Route
context.MapRoute(
MyArea_default,
"MyArea/{controller}/{action}/{id}",
new { action = "Index", id = UrlParameter.Optional },
new string[] { "MyApplication.Areas.MyArea.Controllers" }
);
Credit to Steve Testa, Thanks!.
OrderBy making null records come last Entity Framework and Linq
Ever want to sort your result set but make sure the records with a null sorting column appear last?
See the example below
select x
into grp
select new
{
mydata = grp,
myid = grp.id,
mysortingentity = grp.mysortingentity
}
).OrderBy(x => sortingcolumn == null).ThenBy(x => x.mysortingentity.Name).ThenBy(x => x.myid);
Watch this space, this can easily be transformed into a Extension Method. Check out this link http://tahirhassan.blogspot.com/2010/06/linq-to-sql-order-by-nulls-last.html, this Extension method works for LinqToSQL.
Elmah and MVC 3 Error Handling
I came into an issue where I have Elmah configured on a ASP.NET MVC 3 website. I notice that the errors were not getting logged or emailed as per my configuration settings in web.config.
I found that MVC3 Error Handling was handling all the errors before Elmah could handle the error and report it correctly.
This link was very helpful:
http://thecodersperspective.posterous.com/how-to-get-elmah-and-mvc3-error-handling-to-p
Also when using Elmah on a production environment, its a good idea to lock down the permissions for the elmah.axd handler so only users in a particular role have access to this file. (Assuming that your website is using forms authentication and role provider)
<system.web>
<authorization>
<allow roles="Administrators" />
<deny users="*" />
</authorization>
</system.web>
</location>
And also make sure that this setting is set to false so its not available to remote users.
Max number of words in textarea using jQuery
Here is a little code snippet to set a maximum of words in a text area element.
This could be turned into a jQuery function or refactored to be better reused, but there is a sample just to get you going.
var wordArray = this.value.split(/[\s\.\?]+/); //Split based on regular expression for spaces
var maxWords = 5; //max number of words
var total_words = wordArray.length; //current total of words
var newString = "";
//Roll back the textarea value with the words that it had previously before the maximum was reached
if (total_words > maxWords+1) {
for (var i = 0; i < maxWords; i++) {
newString += wordArray[i] + " ";
}
this.value = newString;
}
});
Using UserControl.RenderControl()
Perhaps you want to reuse a usercontrol in a email? or you need to generate the html from a usercontrol for a specific purpose?
Firstly, here is the code to render the html from a user control to a string builder object:
Using sw As New StringWriter(sb)
Using htmlTw As New HtmlTextWriter(sw)
Dim ucUserControl As UCUserControl = LoadControl("myUserControl.ascx")
ucUserControl .Visible = True
ucUserControl .Display() 'This is a custom method to do some processing
ucUserControl .RenderControl(htmlTw)
End Using
End Using
Secondly, make sure that you set enableEventValidation=”false” on the page you are generating the html from. You will get an error if this is enabled. You could extend this futher by adding a boolean variable to the page and only disabling Event Validation while rending the control.
Thirdly, override the VerifyRenderingInServerForm method and make it doing “nothing”. If you do not do this, you will get an error saying that you have no form sever tag in your user control.
'Do nothing
End Sub


