Tuesday, March 29, 2011

Master-Detail with the GridView, DetailsView and jQuery's ThickBox

Master-Detail with the GridView, DetailsView and jQuery's ThickBox: "

And so I continue on messing around with jQuery and looking into ways I can use it in my WebForms applications.  Something I sheepishly admitted to in my last post was that I don't want to use 3 client side libraries {ASP.NET AJAX, AjaxControlToolkit, jQuery}, yet I never really took the time to see what the size of the core jQuery library is.  And for that matter I haven't really looked at what scripts some of my favorite AjaxControlToolkit controls are pulling down either.  I also assumed there is a bunch of overlap between jQuery and ASP.NET AJAX, but I haven't looked into that either. 

So I thought I would check some of this out and along the way rebuild my Master-Detail with the GridView, DetailView and ModalPopup Controls and replace the ModalPopup with jQuery's ThickBox.  I found it pretty interesting - read on to see how it went.

Live Demo | Download

ThickBox Demo

What I Was Looking For

While building this sample I decided I would keep my eye out for the following things ...

  1. Size Comparison (minified, but not gzipped) of AjaxControlToolkit's ModalPopup versus jQuery's ThickBox
  2. Installation / Configuration 
  3. Programming Model / API 
  4. ASP.NET AJAX Integration 

 

jQuery's ThickBox

If you are not familiar with jQuery's ThickBox = here the 2 sentence description taken from the ThickBox home page ...

ThickBox is a webpage UI dialog widget written in JavaScript on top of the jQuery library.  Its function is to show a single image, multiple images, inline content, iframed content, or a content server through AJAX in a hybrid model

Size Comparison

This is what I was most interested in.  I get a lot of comments to my AjaxControlToolkit posts that comment on how large the client side footprint of the Toolkit is.  And I honestly don't really know how to respond - mostly because I don't have a baseline that I can compare it to.  And jQuery's ThickBox and the Toolkit's ModalPopup is not exactly like comparing apples to apples, but I think it is close enough to get a feeling for things.

So here are the numbers ...

 FileSize (Compressed)
ThickBox  
 jQuery.js55KB
 ThickBox.js/ThickBox.css10KB
Total 65KB
ModalPopup  
 Common.js28KB
 BaseScripts.js11KB
 RoundedCornersBehavior.js27KB
 Timer.js1KB
 DropShadowBehavior.js7KB
 DynamicPopulateBehavior.js6KB
 DragDropScripts.js23KB
 DynamicPopulateBehavior.js6KB
 Floatingehavior.js4KB
 ModalPopupBehavior.js18KB
Total 131KB

 

The ModalPopup and its supporting scripts is just over twice the size of the jQuery plus ThickBox combo.  Interesting.  If you are curious about where I got the numbers from - I used the file system to get the jQuery/ThickBox numbers and Reflector to look at the sizes of the Toolkit's supporting JavaScript files (they are embedded resources). 

image

I should mention though that although both the ModalPopup and ThickBox JavaScript files are both minified - this wasn't done with the same tool.  And to my novice eye, it looks like the one jQuery uses does a better job.  So you should keep this in mind when interpreting these numbers.  Also, this is without gzipping - so these numbers will both be smaller after this is applied as well.

 

ThickBox Installation and Configuration

If you want to use the ThickBox, you have to do the following things ...

1.  Download the latest version of jQuery and add a script reference to your page

2.  Download thickbox.js, thickbox-compressed.js, thickbox.css and the loadingAnimation.gif graphic and place them in your web site.  I included both the compressed and non-compressed versions of thickbox just in-case I wanted to debug any issues I ran into. 

After steps #1 and #2, your site should look something like this (the highlighted are the jQuery/ThickBox components) ...

image

And your page will include the following JavaScript and CSS references.

image

3.  Next, you need to update the thickbox-compressed.js and thickbox.js files to include the location of the loading graphic you would like to display while the ThickBox is loading.  The ThickBox component displays an animated gif while the ThickBox is being displayed.  By default the component assumes the location of this graphic is images/loadingAnimation.gif.  So if this isn't the location on your web server you will have to manually update the thickbox.js and thickbox-compressed,js files and update this variable.  In my app, I am storing the graphic at _assets/img/loading.gif so I updated the thickbox JavaScript files to use this path instead of the default one.

 

   1: //  change this ...
   2: var tb_pathToImage = "images/loadingAnimation.gif";
   3:  
   4: //  to this ...
   5: var tb_pathToImage = "_assets/img/loading.gif";

 

 

Programming Model

The ThickBox programming model works as follows ...

You add an A or INPUT to your page and adorn it with the thickbox CSS class.  Then, like magic, when the element is clicked the ThickBox is displayed.  Here is a real simple example that shows an image in a ThickBox - when the A is clicked the nested IMG element is displayed in a ThickBox.

   1: <a href="images/single.jpg" title="add a caption to title attribute / or leave blank" class="thickbox">
   2:     <img src="images/single_t.jpg" alt="Single Image"/>
   3: </a>

 

The ThickBox also supports other scenarios too - like displaying some in-line content from the page inside the ThickBox (like a hidden DIV or something) as well as showing an IFRAME in the ThickBox.  These other scenarios require some additional parameters - and the ThickBox uses some of the existing INPUT and A attributes to specify these values ...

  • title - you can use the A or INPUT's title attribute to specify a title for the ThickBox
  • A.href and INPUT.alt - the value of this attribute is parsed into some of the parameters that control how the ThickBox behaves

For example, here is how you would use the ThickBox to display some hidden content on your page...

  • The href attribute of the A element contains the encoded parameters
  • #TB_inline tells the ThickBox that the content to be displayed exists some where on the current page
  • height/width specify the height and width of the ThickBox
  • inlineId is the ID of the element that will be displayed
  • modal specifies that the ThickBox will be displayed modally
   1: <a href="#TB_inline?height=155&width=300&inlineId=hiddenModalContent&modal=true" class="thickbox">Show hidden modal content.</a>

 

And here is a very simple example of an anchor that when clicked displays some hidden content.

   1: <a href="#TB_inline?height=50&width=300&inlineId=hiddenModalContent" title="Simple Demo" class="thickbox">Show hidden content.</a>
   2: <div id="hiddenModalContent" style="display:none;">
   3:     <div style="text-align:center;">Hello ThickBox!</div>
   4: </div>

 

And here is what it looks like.  In this sample, the dialog isn't modal so it can be closed via the Close or pressing the Escape key or just by clicking any where else on the page.

image

 

ASP.NET AJAX Integration

And now of the real question - how well does the ThickBox work with my WebForms apps?  Well, I tried porting over my previous sample of using the ModalPopup to edit rows in a GridView.  Here is how it went ...

  • I started down the path of using the ThickBox to display some hidden inline content, but that didn't work out to well.  The ThickBox takes the contents of the inline content and moves the DOM elements out to an immediate child of the document's BODY element.  That doesn't work too well when used with WebForms because I need everything contained within the FORM element for my page to work properly. 

Check it out - below is what the DOM looks like.  See how the FORM element and the TB_window DIV are siblings - that isn't going to work. 

image

But this isn't exactly a show stopper either - I can still move my detail content over to a separate page and have the ThickBox load it up using an IFRAME.  So that's were I went next.

  • I updated the markup for my main GridView and changed the last column to render an A tag with all of the parameters I need to tell the ThickBox to load up a new page in an IFRAME.  Remembering how the ThickBox parses the arguments from the anchor's href and title attributes, I used a databinding expression to build out the values for these attributes. 

When the dialog shows I want to include the Customer name in the title, so I am using the ContactName property to build the title value.  I also want to show the Detail.aspx page passing the current rows ID value as an argument.  So I again use a databinding expression to set the href attribute to the value I want.

   1: <asp:GridView ID="gvCustomers" runat="server" DataSourceID="odsCustomerList" CssClass="datagrid" GridLines="None" AutoGenerateColumns="false">
   2:     <Columns>
   3:         <asp:BoundField DataField="ID" HeaderText="ID" ReadOnly="true" />
   4:         <asp:BoundField DataField="CompanyName" HeaderText="Company" ReadOnly="true" />
   5:         <asp:BoundField DataField="ContactName" HeaderText="Name" ReadOnly="true" />
   6:         <asp:BoundField DataField="ContactTitle" HeaderText="Title" ReadOnly="true" />                
   7:         <asp:BoundField DataField="Address" HeaderText="Address" ReadOnly="true" />                
   8:         <asp:BoundField DataField="City" HeaderText="City" ReadOnly="true" />                
   9:         <asp:TemplateField>
  10:             <ItemTemplate>
  11:                 <a id="btnShowPopup" runat="server" class="thickbox" 
  12:                     title='<%# Eval("ContactName", "Details for {0}") %>' 
  13:                     href='<%# Eval("ID", "Detail.aspx?ID={0}&TB_iframe=true&height=220&width=400") %>'>Edit</a>                
  14:             </ItemTemplate>
  15:         </asp:TemplateField>                        
  16:     </Columns>                    
  17: </asp:GridView>
  • Next, I created the Detail.aspx page that displays the detailed information for a given Customer.  This page requires a single parameter, ID, that passed via the querystring.  This page displays the Customer detail information in a DetailsView and allows you to edit the values.  The page has a single Save button that when clicked ensures the page is valid, saves the changes back to the data store, and closes the ThickBox window.

The interesting part here is that the Detail page is responsible for closing itself after the user presses the Save button.  So to accomplish this, I need to register a small script that will run after the Save has completed that will close the ThickBox.  To get this bit of work done, I first created a JavaScript function on the main page called updated that closes the ThickBox window using the ThickBox's tb_remove function.  Next, I need to let the main grid know that it needs to refresh itself (otherwise you wouldn't see any of the changes you just made).  I handle this by setting up a hidden Refresh button that when clicked causes the main grid to refresh itself.

Here is what the updated function looks like ... 

   1: function updated() {
   2:     //  close the popup
   3:     tb_remove();
   4:     
   5:     //  refresh the update panel so we can view the changes  
   6:     $('#<%= this.btnRefreshCustomers.ClientID %>').click();      
   7: }

 

And here is what the Detail page's Save event handler looks like ...

   1: protected void BtnSave_Click(object sender, EventArgs args)
   2: {
   3:     if (this.Page.IsValid)
   4:     {
   5:         //  move the data back to the data object
   6:         this.dvCustomerDetail.UpdateItem(false);
   7:         
   8:         //  register the script to close the popup
   9:         this.Page.ClientScript.RegisterStartupScript(typeof(detail_aspx), "closeThickBox", "self.parent.updated();", true);
  10:     }
  11: } 

 

  • And I actually need make one more modification.  My customer grid is contained within an UpdatePanel.  And even though this grid doesn't sorting or paging, the example as-is wouldn't work too well after the user triggered an event that causes the UpdatePanel to refresh itself.  The reason why is because the ThickBox works by fetching all of the A and INPUT's that contain the thickbox class - and this selection occurs just after the page is initially loaded.  And it doesn't happen again after that.  So we need to add a special piece of logic that will reapply the ThickBox to the A elements that are contained within our UpdatePanel.  And here is the bit of JavaScript that I am using to do this ...
   1: function pageLoad(sender, args) {
   2:     if(args.get_isPartialLoad()){
   3:         //  reapply the thick box stuff
   4:         tb_init('a.thickbox');
   5:     }
   6: }

 

This is again scanning the complete page for anchor elements with the thickbox class and adding the ThickBox behavior to them.  This bit of logic could be optimized because ...

  • We don't need to scan the complete page again, just inside any UpdatePanels that were refreshed.  This is where it might be useful to use a Sys.Component to auto-magically keep track of this stuff for us.  We could easily hook into some of the ASP.NET client side page events and reapply the ThickBox stuff to any elements that contained within an UpdatePanel.  This is pretty similar to the approach I used here to maintain scroll position across partial post-backs.    

 

Conclusion

So, after all of this, here are a few things I found interesting.

  • When I saw all of the scripts the ModalPopup required I couldn't help think it might be interesting if there was a way for the control to only render the scripts it needed to function based on the features you are currently using.  That would be useful.
  • It took me a while to learn the ThickBox's API.  Encoding the parameters into the href is crafty, but it also makes the features hard to discover without documentation.  The Toolkit has a very consistent API.
  • None of the jQuery stuff is going to have a server side API - so when using jQuery from my WebForms I am going to be injecting quite a bit more JavaScript from the page's codebehind than I am used to.
  • I didn't like how the ThickBox forces me to separate my detail widget into a separate page.  But the reality is that most of the jQuery stuff is not written with WebForms in mind.  So running into this stuff might be the norm when trying to use these scripts.

Also, keep in mind this is just one sample.  In my mind, this doesn't tell me to replace the Toolkit with jQuery or vice versa. 

 

That's it.  Enjoy!   

"

Monday, March 28, 2011

C# Programming Language (Covering C# 4.0), The (4th Edition)

C# Programming Language (Covering C# 4.0), The (4th Edition): "






The popular C# programming language combines the high productivity of rapid application development languages with the raw power of C and C++. Updated to cover the new features of C# 4.0, including dynamic binding, named and optional parameters, and covariant and contravariant generic types, this release takes the language to the next level by adding the ability to cleanly write programs that don’t rely on static type definitions. This allows dynamic programming languages such as Python, Ruby, and JavaScript to feel native to C#. The C# Programming Language, Fourth Edition, continues to be the authoritative and annotated technical reference for C# 4.0.




 


Written by Anders Hejlsberg, the language’s architect, and his colleagues, Mads Torgersen, Scott Wiltamuth, and Peter Golde, this volume has been completely updated for C# 4.0. The book provides the complete specification of the language, along with descriptions, reference materials, code samples, and annotations from twelve prominent C# gurus.


 


The many annotations bring a depth and breadth of understanding rarely found in any programming book. As the main text of the book introduces the concepts of the C# language, cogent annotations explain why they are important, how they are used, how they relate to other languages, and even how they evolved.


 


This book is the definitive, must-have reference for any developer who wants to understand C#. With annotations from: Brad Abrams, Joseph Albahari, Krzysztof Cwalina, Jesse Liberty, Eric Lippert, Christian Nagel, Vladimir Reshetnikov, Marek Safar, Chris Sells, Peter Sestoft, Jon Skeet, and Bill Wagner.


 


 




Download Here


If you liked this post, buy me a beer. (Suggested: $3 a beer or $7.5 for a pitcher)



"

Sunday, March 27, 2011

30 Developer and Designer Apps for iPhone, Android and BlackBerry

30 Developer and Designer Apps for iPhone, Android and BlackBerry: "Thumb_thumb

There has been a steady stream of mobile apps tailored to web developers and designers, transforming mobile devices into a virtual Swiss-Army Knife of tool applications.

I looked at the top one-hundred developer-oriented and designer-oriented apps with the highest user ratings and the most downloads for each of the top three mobile devices: Apple’s iPhone, Google’s Android phones and Research in Motion’s BlackBerry. I then handpicked from that group of one hundred to focus on twenty highly rated and popular developer and designer apps for each phone.

Download these apps and start developing and designing while on the go.

Developer Apps


Developer’s Tool Kit. A full-featured toolbox for any web developer or programmer featuring a comp...

"

Thursday, March 17, 2011

jQuery File Upload Plugin With Drag ‘n’ Drop Support

jQuery File Upload Plugin With Drag ‘n’ Drop Support: "

jQuery File Upload is a plugin for the popular JavaScript framework that helps handling file uploads.


The plugin is based on open standards like HTML5-JavaScript and doesn't use Flash. For legacy browsers it falls back to an Ajaxed-like iframe-PHP solution.


jQuery Drag Drop File Upload


Multiple files can be selected, they can even be drag 'n' dropped from the desktop and the upload process can start automatically or with an event (like clicking the "upload" button).


A progress bar can display the status and uploads can be canceled anytime.


It is possible to use multiple instances of the plugin in the same page and jQuery File Upload is compatible with any server-side application platform.


Special Downloads:

Ajaxed Add-To-Basket Scenarios With jQuery And PHP

Free Admin Template For Web Applications

jQuery Dynamic Drag’n Drop

ScheduledTweets


Advertisements:

Professional XHTML Admin Template ($15 Discount With The Code: WRD.)

Psd to Xhtml

SSLmatic – Cheap SSL Certificates (from $19.99/year)




"

Thursday, March 10, 2011

The Ultimate 20 Usability Tips for Your Website

The Ultimate 20 Usability Tips for Your Website: "Usability is ridiculously important to your website. It doesn’t matter how cool your website looks or how amazing your content is if visitors can’t quickly, easily, and enjoyably access and use it. Many of them will eventually just give up and look elsewhere."

Ecommerce Fraud Management Systems: The What The Why and The How

Ecommerce Fraud Management Systems: The What The Why and The How: "

Card-not-present credit card fraud cost online merchants 0.9% of revenue in 2010 (down from 1.4% in 2008 and 3.6% in 2000) according to research by Cybersource.


The expense of chargebacks, unrecoverable transfers, unnecessary shipping costs and human resources to investigate disputes add up, and a company’s goodwill can be damaged with banks when fraud rates are higher than average. Chargeback rates higher than your merchant bank’s acceptable threshold may cause you to lose your merchant account, and make it more difficult for you to open one with another bank. And processing fraudulent transactions that a cardholder must dispute tarnishes your brand name in their eyes, and could spark negative word of mouth.


Fraud management systems, both manual and automated, aim to curb fraud losses and protect cardholders from unauthorized use of their accounts. Last post we covered 3D Secure cardholder authentication (e.g. Verified by Visa, MasterCard Secure Code), which is only one tool in your fraud management arsenal, and should not be relied upon alone. Today we examine these tools, and what you should consider when developing your fraud management solution.


Basic automated fraud management tools


AVS


Address Verification Service, or AVS, is a tool that checks a customer’s input name, address and card number details against a database of addresses on file with card issuing banks. AVS is not available for all countries, issuing banks and card types. For example, American Express only supports AVS in the US.


While AVS is effective at preventing fraudsters, it’s also prone to reject good orders, as it requires an exact match of the customer’s billing address. For example, an address #33-1234 Suchandsuch Road may be rejected if the on-file address is 1234 Suchandsuch Rd Apt. 33. If a bona fide customer attempts the address multiple times “incorrectly,” her card may be locked out of use temporarily.


Another problem is an AVS mismatch may not prevent the authorization from appearing on the cardholder’s statement. The issuing bank can hold the authorized amount for 3 to 7 days unless contacted by the customer. But confused and irate customers who find the charges may complain to your customer service.


It’s a good idea to provide error messaging when a mismatch occurs that explains the address must be exactly as on the cardholder’s statements. (Test it in different browsers to make sure it’s readable!)



Because it’s easy to reject good orders and accept fraudulent ones, you don’t want to rely on AVS alone. Mismatches and partial matches do not have to be rejected, they may be flagged for manual review.


CVV


I’ve written about the perils of and workarounds for CVV (aka CSC, CVN, CVC and CVV2) on Get Elastic before. To recap, CVV (card verification value) can create 2 conversion problems. Some customers will not know what CVV is or where to find it. Others fear handing over their security code means it may fall into the wrong hands. Address both these FUDs (fears, uncertainties and doubts) by showing visually where to find the code, and explaining the number will not be stored in your database.



3D Secure


As discussed in detail last post, Verified by Visa, MasterCard Secure Code and their cousins add an extra layer of authentication to the checkout process by means of a personal password/PIN. The main benefit for retailers is the liability shift from the merchant to the issuing bank should the customer file a chargeback. Merchants may also enjoy lower interchange fees for participating in the scheme. However, the extra step in checkout is not always appreciated by customers. Many merchants report a drop in conversion when using 3D Secure.


Again, 3D Secure is not the silver bullet to prevent fraud. Not all card issuing banks participate with Verified by Visa, and not all cardholders have enrolled. Unenrolled cardholders are allowed to opt-out a number of times (variable) before being required to join the program, and identity thieves who are first to use a new card online can set their own passwords.


The decision whether to use 3D Secure depends on a number of things explained last post.


Automated Transactional Risk Scoring


ATRS solutions enable ecommerce systems to identify suspicious behavior, assign a “risk factor” and reject or flag a risky order for manual review. The logic and settings are custom to the online retailer based on past experience and other industry factors. They may be home-grown or third-party (e.g. Cybersource, Ethoca, Accertify). The downside of home-grown solutions is they depend heavily on the trial-and-error experience of your own business. Third-party services that pull data from a large user base are more nimble in detecting fraud trends and can have higher accuracy, but may also come at a much higher cost.


Beyond AVS, CVV, 3D Secure, a variety of other tools may be used by the ATRS including:



  • IP detection Identifies user’s location and checks against known high risk IP and email addresses

  • Device fingerprinting Reads data from and about a device and browser session including true IP address and location (can identify proxies), and whether the device has been involved in previous fraudulent activities. (More features than simple IP detection)

  • Order velocity monitoring Flags orders that have been submitted within a specific time period from one card or IP address

  • Positive lists Records of “good” customers, based on order history

  • Negative lists Known “bad” IPs, card numbers, device IDs, name/address combos, etc. Some banks end up on black lists if they are known to have higher rates of fraud among cards they have issued

  • Shared lists Positive/negative lists shared across companies


Systems are typically tuned to detect suspicious behavior like high dollar value baskets, unusual product mixes (random selection of clothing sizes, for example), large quantities of a single item (especially electronics) and rapid additions to cart.


What one e-tailer considers abnormal may be the norm for other businesses, so automated systems allow rules-based tweaking. For example, a $1,000 order may be suspicious for an electronics store but very common for a furniture shop that sells $5,000 living room sets. A billing and shipping address mismatch is common with gifts and flower delivery sites, but may be more suspicious for others. Some businesses may want to reject orders from certain regions, or flag orders shipping to P.O. boxes, prisons, hotels, schools and hospitals.


Manual Reviews


Despite automation’s virtues, some orders call for manual review. This may involve calling the customer or the customer’s bank, using reverse lookup tools, checking customer records or even using Google Maps and social media to track down a name/address. (Some automated tools have the ability to check email addresses across social networks).


Manual review by humans is obviously more costly and time consuming than automated tools, and using a number of different automated tools *should* reduce costs. However, the more automated tools you use (the average is 7.4 for large ecommerce companies), the more likely an order will be flagged for manual review! Companies that seem to squeak by with a low percentage of manual reviews are likely rejecting good orders, and should understand where the sweet spot lies between resource savings and sales and profits. Ideally, manual reviews should be reserved for orders you want to keep, rather than as a fraud detection method.


What system is right for you?


There’s no one-size-fits-all fraud management solution. Your needs will vary based on your transaction volume, industry, geographic market(s) and rate of fraud, and your options may be limited by staffing or budget (smaller merchants tend to rely solely on manual reviews and the “basic” automated services). The rules you apply to your system will also vary based on the nuances of your individual business. Keep the following in mind when choosing / using a fraud management system:


1. Order rejection rates


Merchants with slimmer margins have more to lose when orders are fraudulent, and tend to have higher order rejection rates. Those with higher margins can absorb more fraud, accepting a higher risk in exchange for faster order processing and shipment, and less false-positives.


2. False-positives


A continual challenge with automated tools is the rejection of legitimate orders (false-positives). Tools should be continually tweaked when false positives are identified. Julie Fergerson of Ethoca recommends you monitor your “order resuscitation” rate, along with customer complaints to your call center on rejected orders. “It should be very low.”


3. System maintenance


In addition to false-positive feedback, tools require continual updating, both with new technologies to combat fraud and with information you gather from day-to-day operations. For example, daily review of declines (both internal rejections and those declined by issuing banks) can help you discover authorization problems that may exist, or commonalities among purchase behavior or other characteristics of declined orders. Fergerson says “even a purchased risk engine needs to be constantly analyzed. Otherwise, they can be worse than just guessing.”


4. Staffing


Insufficient staffing can delay orders, leading to unhappy customers and more WISMO (where is my order) calls to your customer service team. Ill-trained staff can also reject good orders and let bad ones slip through. Some fraud management vendors offer outsourced manual review staff, which may have some efficiencies (scale up and down as needed), and in some cases, better trained staff.


The takeaway


There are many weapons available to help your quest for fighting fraud, but there’s a fine balance between stopping bad orders and preserving good ones. No matter what your fraud management system looks like, it requires continual maintenance to be effective. You should be continually adjusting rules and processes based on the overall online fraud environment and your own learnings to ensure you’re maximizing profits and minimizing losses.


Looking for help with your ecommerce strategy and site optimization? The Elastic Path research and consulting division is available to enterprises selling digital goods and services. For more information, visit us at http://elasticpath.com/ecommerce-consulting/ or contact us at consulting@elasticpath.com.

"

Tuesday, March 8, 2011

7 jQuery plugins you should not miss

7 jQuery plugins you should not miss: "Jquery is a client based application that runs on user side i.e. at your web browser. One of the best things about jQuery is that it allows users to add interactive functionalities to their websites. Interactiveness allows you to increase the functionality of your website by producing robustness and dynamicity in your website. To understand [...]


"