<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.3.2">Jekyll</generator><link href="/feed.xml" rel="self" type="application/atom+xml" /><link href="/" rel="alternate" type="text/html" /><updated>2024-04-06T20:56:27+00:00</updated><id>/feed.xml</id><title type="html">Kevin Glowacz</title><subtitle>A place to put my thoughts.</subtitle><entry><title type="html">Rails Debugging with VS Code and puma-dev</title><link href="/2024/03/10/rails-debugging-with-puma-dev.html" rel="alternate" type="text/html" title="Rails Debugging with VS Code and puma-dev" /><published>2024-03-10T00:00:00+00:00</published><updated>2024-03-10T00:00:00+00:00</updated><id>/2024/03/10/rails-debugging-with-puma-dev</id><content type="html" xml:base="/2024/03/10/rails-debugging-with-puma-dev.html">&lt;p&gt;Rails debugging has gotten really good lately; a new Rails project automatically includes the &lt;a href=&quot;https://github.com/ruby/debug&quot;&gt;ruby debug&lt;/a&gt; gem, and it is enabled by default in development. The &lt;a href=&quot;https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem&quot;&gt;Rails instructions for it&lt;/a&gt; work great if you start your server with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;rails server&lt;/code&gt; and want to use the command line debugging interface. However, I prefer to run all my apps with &lt;a href=&quot;https://github.com/puma/puma-dev&quot;&gt;puma-dev&lt;/a&gt; to give myself a nice local test domain name, and I prefer to use vscode for debugging.&lt;/p&gt;

&lt;p&gt;Luckily, with just a little bit of code, we can configure the debug gem to automatically open a named debug socket on app launch that we can attach to via vscode and the &lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=KoichiSasada.vscode-rdbg&quot;&gt;vscode-rdbg extension&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Add the following lines to your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;config/environments/development.rb&lt;/code&gt; file:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# config/environments/development.rb&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;Rails&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;application&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;server&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;DEBUGGER__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;nonstop: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;session_name: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;my-app-server&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;Rails&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;application&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;console&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;DEBUGGER__&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;nonstop: &lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;session_name: &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;my-app-console&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;I want to call out a little known Rails feature that I’m taking advantage of here. The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.console&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.server&lt;/code&gt; code blocks are handy built in Rails methods that facilitate running code on boot specifically when in the context of a console session or server session. By using them, I can easily specify different debug session names for each context.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DEBUGGER__.open&lt;/code&gt; is the method to manually open a debug session. By specifying &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nonstop: true&lt;/code&gt; we are telling the debugger to not stop the process upon launch which is important so that the app can run like normal if there are no attached debug sessions or breakpoints. The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;session_name&lt;/code&gt; parameter allows a custom string to be appended to the unix socket and is used to identify the session in vscode when attaching to the debugger while multiple sessions are open.&lt;/p&gt;

&lt;p&gt;To attach to the debugger in vscode, you will need to install the &lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=KoichiSasada.vscode-rdbg&quot;&gt;vscode-rdbg extension&lt;/a&gt; and add a launch configuration to your &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.vscode/launch.json&lt;/code&gt; file. Here is an example of what that might look like:&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;version&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;0.2.0&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;configurations&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;rdbg&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Attach with rdbg&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;request&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;attach&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now when you start the debug session in vscode, if there is more than one active debug session you will be prompted to select which one you want to attach to. The custom session names make it easy to find the right one. You can then set breakpoints and inspect variables as you would expect.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/debugging/select_port.png&quot; alt=&quot;Select a debug session&quot; /&gt;&lt;/p&gt;</content><author><name>Kevin</name></author><summary type="html">Rails debugging has gotten really good lately; a new Rails project automatically includes the ruby debug gem, and it is enabled by default in development. The Rails instructions for it work great if you start your server with rails server and want to use the command line debugging interface. However, I prefer to run all my apps with puma-dev to give myself a nice local test domain name, and I prefer to use vscode for debugging.</summary></entry><entry><title type="html">Hacking the Messages Database</title><link href="/2023/12/20/hacking-the-messages-database.html" rel="alternate" type="text/html" title="Hacking the Messages Database" /><published>2023-12-20T00:00:00+00:00</published><updated>2023-12-20T00:00:00+00:00</updated><id>/2023/12/20/hacking-the-messages-database</id><content type="html" xml:base="/2023/12/20/hacking-the-messages-database.html">&lt;p&gt;With the release of iOS 17.2 and macOS 14.2, there is now the ability to see a count of the number of chat messages stored in iCloud. A bunch of people were comparing counts and what not both on social media and in work chats, so I decided to take a look at my count too. I noticed that for some reason my number was an order of magnitude higher than everyone else’s. While many people had hundreds of thousands of messages, I had 2.9 Million. I am not a popular individual, so I knew something had to be wrong. I decided to investigate by hacking the messages database.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/hacking-messages/icloud_count.png&quot; alt=&quot;Messages iCloud sync status showing 2.9 Million messages&quot; /&gt;&lt;/p&gt;

&lt;p&gt;After some internet searching, I found that the messages database on the Mac is stored at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;~/Library/Messages/chat.db&lt;/code&gt;. It’s a pretty simple &lt;a href=&quot;https://www.sqlite.org/index.html&quot;&gt;SQLite&lt;/a&gt; database that can be accessed via a number of different db utilities including the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sqlite3&lt;/code&gt; command line tool. The trick is that whatever app is accessing the database needs to be granted &lt;a href=&quot;https://support.apple.com/guide/security/controlling-app-access-to-files-secddd1d86a6/web&quot;&gt;full disk access&lt;/a&gt; in the security settings. My preferred tool for looking at databases is &lt;a href=&quot;https://tableplus.com&quot;&gt;TablePlus&lt;/a&gt;. After a quick group by query into the message table, it became clear that the vast majority of these messages are from SMS.&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;select&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;service&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;count&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;from&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;message&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;group&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;by&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/hacking-messages/messages_by_service.png&quot; alt=&quot;TablePlus screenshot showing query results: 887 Jabber, 2,830,344 SMS, 120,113 iMessage&quot; /&gt;&lt;/p&gt;

&lt;p&gt;(Wow, look at those old Jabber messages, I had no idea they were still in there)&lt;/p&gt;

&lt;p&gt;Honing in on the SMS messages, I found that 2.8 Million of these messages contained no text content, and had a value of 39 in the error column. Also they all seemed to have a date value of 489239966000000000. &lt;a href=&quot;https://apple.stackexchange.com/questions/421665/how-specificially-do-i-read-a-chat-db-file&quot;&gt;It appears&lt;/a&gt; that this value is the number of nanoseconds since 2001-01-01, so it correspond to Sunday, July 3, 2016. I have no idea what’s going on with the date. Maybe it is some sort of default value, or my maybe account had some sort of bug at that time and flooded my database with these erred messages.&lt;/p&gt;

&lt;p&gt;My next thought is, of course, how do I clean up just these specific messages? I want them removed everywhere, not just my Mac, but iCloud too. These messages are not associated with any particular chat, so I can’t just use the Messages UI, instead I try to see if I can just delete them from the database. I try the following query:&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;DELETE&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;&quot;message&quot;&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;WHERE&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;&quot;ROWID&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;goes&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;here&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;When I tried this, I got the error: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;no such function: after_delete_message_plugin&lt;/code&gt;. It appears that this database has a number of triggers defined that do various things when items are deleted from the database. In this case, the message delete trigger is trying to call a function, but apparently this function isn’t defined in the database. I learned that SQLite has this feature called &lt;a href=&quot;https://sqlite.org/appfunc.html&quot;&gt;Application-Defined SQL Functions&lt;/a&gt;. This feature allows apps to define custom SQL functions that call back into the application code. I assume that Messages must define the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;after_delete_message_plugin&lt;/code&gt; function somewhere within the app code, so I don’t have access to it when talking directly to the db outside of the app context. I searched for a bit to see if there was some way to override this function, but I couldn’t find anything quickly. I decided to try a different approach. What if instead, I associate each of these messages with a chat, then delete the chat in the app!&lt;/p&gt;

&lt;p&gt;In order to do this, I found a chat that I will willing to delete: one of those SMS shortcode chats that sent me a one time access code or something else automated. I then crafted a query that would associate all these bad SMS messages with that chat through the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;chat_message_join&lt;/code&gt; table.&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;INSERT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;INTO&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;chat_message_join&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;chat_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;message_id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;message_date&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;SELECT&lt;/span&gt;
	&lt;span class=&quot;mi&quot;&gt;1018&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;ROWID&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
	&lt;span class=&quot;nb&quot;&gt;date&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;message&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;LEFT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;OUTER&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;JOIN&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;chat_message_join&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;ON&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;chat_message_join&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;message_id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ROWID&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;LEFT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;OUTER&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;JOIN&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;chat_recoverable_message_join&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;ON&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;chat_recoverable_message_join&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;message_id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;message&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ROWID&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;WHERE&lt;/span&gt;
	&lt;span class=&quot;nb&quot;&gt;date&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;489239966000000000&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;AND&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;account_guid&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;IS&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NULL&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;AND&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;service&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;SMS&apos;&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;AND&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;account&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;IS&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NULL&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;AND&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;text&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;IS&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NULL&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;AND&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;error&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;39&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;AND&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;handle_id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;AND&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;attributedBody&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;IS&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NULL&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;AND&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;chat_message_join&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;chat_id&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;IS&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NULL&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;AND&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;chat_recoverable_message_join&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;chat_id&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;IS&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NULL&lt;/span&gt;
	&lt;span class=&quot;k&quot;&gt;AND&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;balloon_bundle_id&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;IS&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NULL&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;ORDER&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;BY&lt;/span&gt;
	&lt;span class=&quot;n&quot;&gt;ROWID&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;LIMIT&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;500000&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This query selects all the bad SMS messages (I added some extra conditions just to be extra sure I wasn’t deleting anything that could be important), and inserts them into the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;chat_message_join&lt;/code&gt; table with the chat ID of the chat I want to delete (in this case 1018). By joining to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;chat_message_join&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;chat_recoverable_message_join&lt;/code&gt; tables and selecting on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;NULL&lt;/code&gt; values, I also ensure that I don’t insert any new message joins for messages that are already joined to a chat. I also limit the query to 500,000 messages at a time, because I have no idea what Messages will do when trying to delete that extreme number of messages all at once.&lt;/p&gt;

&lt;p&gt;After relaunching Messages, the chat in question now shows all these blank messages in their erred state within a chat window.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/hacking-messages/messages_not_delivered.png&quot; alt=&quot;Messages screenshot showing erred SMS messages&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Now for the moment of truth, I delete the chat.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/hacking-messages/delete_chat.png&quot; alt=&quot;Messages screenshot showing delete icon&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Message got stuck in this exact state showing the trash can in a red background for many hours while the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;IMDPersistenceAgent&lt;/code&gt; process pegged a CPU core. Whatever the app is doing, it seems like it must be doing it synchronously, for each message, maybe it has something to do with that &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;after_delete_message_plugin&lt;/code&gt; function.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/hacking-messages/imd_persistence_agent.png&quot; alt=&quot;Activity Monitor screenshot showing IMDPersistenceAgent process pegging a CPU core&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Eventually, the process finished and the chat was deleted. It showed in the Deleted Chat view with all of the messages still linked, and I could see all the associations in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;chat_recoverable_message_join&lt;/code&gt; table.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/hacking-messages/recently_deleted.png&quot; alt=&quot;Recently Deleted screenshot showing 511,007 linked messages&quot; /&gt;&lt;/p&gt;

&lt;p&gt;(I did a 1,000 and 10,000 test batch before doing the full 500,000)&lt;/p&gt;

&lt;p&gt;Deleted messages say they take 30 days before the are no longer recoverable on device, and then 10 more days after that before they are deleted from iCloud. To speed this process up, I force deleted the chat out of Recently Deleted. This seemed to remove all the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;chat_recoverable_message_join&lt;/code&gt; associations as well as the original &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;message&lt;/code&gt; records from the local database. The operation completed very quickly (relative to the process of deleting the chat in the first place).&lt;/p&gt;

&lt;p&gt;After this I forced iCloud syncs on all my devices, and this put my phone into a very hot state. The battery drained very quickly and I even managed to catch this interesting error message:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/hacking-messages/charging_on_hold.png&quot; alt=&quot;Charing on hold. Charing will resume when iPhone returns to normal temperature&quot; /&gt;&lt;/p&gt;

&lt;p&gt;iCloud still reports the same number of message in the cloud and my other computer has not had these messages deleted from its local database, so I’m not entirely sure yet if this actually did anything useful, but the fact that my phone overheated leads me to believe that it at least did &lt;em&gt;something&lt;/em&gt;. I guess I’ll find out in 10 days if the iCloud count goes down or not.&lt;/p&gt;

&lt;p&gt;Just for fun, I turned off the iCloud sync on the original Mac and then turned it back on. The sync process re-download all those deleted messages, so I’m thinking that might not be a good sign that this worked the way I wanted. I’ll make an update here after 10 days or if I learn anything new.&lt;/p&gt;

&lt;p&gt;Do you have any contacts on the Message team or have any insight here? If so please &lt;a href=&quot;https://hachyderm.io/@kjg&quot;&gt;reach out to me on Mastodon&lt;/a&gt;. I have also filed this as FB13485744&lt;/p&gt;

&lt;p&gt;Update: 2023-01-09&lt;/p&gt;

&lt;p&gt;It has been more than 10 days and the number of messages in iCloud has not gone down, so apparently my process for deleting these messages did not work.&lt;/p&gt;</content><author><name>Kevin</name></author><summary type="html">With the release of iOS 17.2 and macOS 14.2, there is now the ability to see a count of the number of chat messages stored in iCloud. A bunch of people were comparing counts and what not both on social media and in work chats, so I decided to take a look at my count too. I noticed that for some reason my number was an order of magnitude higher than everyone else’s. While many people had hundreds of thousands of messages, I had 2.9 Million. I am not a popular individual, so I knew something had to be wrong. I decided to investigate by hacking the messages database.</summary></entry><entry><title type="html">Checking on the status of the TouchPad, polling for changes on a web page</title><link href="/2011/08/25/checking-on-status-of-touchpad-polling.html" rel="alternate" type="text/html" title="Checking on the status of the TouchPad, polling for changes on a web page" /><published>2011-08-25T16:52:00+00:00</published><updated>2011-08-25T16:52:00+00:00</updated><id>/2011/08/25/checking-on-status-of-touchpad-polling</id><content type="html" xml:base="/2011/08/25/checking-on-status-of-touchpad-polling.html">With all the hype&amp;nbsp;surrounding&amp;nbsp;the price drop of the HP TouchPad, I have found myself spending a lot of time trying to find a store with one in stock. At one point I saw that HP&apos;s own store listed the touchpad as &quot;coming soon&quot; after most stores had run out of stock. Instead of refreshing the page on my own I wrote a script to do it for me.&lt;br /&gt;
&lt;br /&gt;
This script loads the product page every 30 seconds and checks the button area to see if it still says coming soon or if it has the out of stock image. When it no longer finds either of those things, it has my computer say &quot;alert&quot; over and over again (in an australian accent, because who doesn&apos;t &lt;a href=&quot;http://www.tuaw.com/2011/07/24/os-x-lion-introduces-new-multilingual-high-quality-text-to-spe/&quot;&gt;change their speech voice&lt;/a&gt; to australian) and automatically loads the product page in Chrome Canary for me.&lt;br /&gt;
&lt;br /&gt;
I&apos;m sure there are many others like it, but this one is mine.&amp;nbsp;Check it out. Use it, fork it, modify it for your own needs. I license this under &lt;a href=&quot;http://sam.zoy.org/wtfpl/&quot;&gt;WTFPL&lt;/a&gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;script type=&quot;text/javascript&quot; src=&quot;https://gist.github.com/25bc348bf00f51e95991.js&quot;&gt;&lt;/script&gt;</content><author><name>Kevin</name></author><category term="TouchPad" /><category term="script" /><summary type="html">With all the hype&amp;nbsp;surrounding&amp;nbsp;the price drop of the HP TouchPad, I have found myself spending a lot of time trying to find a store with one in stock. At one point I saw that HP&apos;s own store listed the touchpad as &quot;coming soon&quot; after most stores had run out of stock. Instead of refreshing the page on my own I wrote a script to do it for me. This script loads the product page every 30 seconds and checks the button area to see if it still says coming soon or if it has the out of stock image. When it no longer finds either of those things, it has my computer say &quot;alert&quot; over and over again (in an australian accent, because who doesn&apos;t change their speech voice to australian) and automatically loads the product page in Chrome Canary for me. I&apos;m sure there are many others like it, but this one is mine.&amp;nbsp;Check it out. Use it, fork it, modify it for your own needs. I license this under WTFPL.</summary></entry><entry><title type="html">A simple irc bot in ruby</title><link href="/2009/03/07/simple-irc-bot-in-ruby.html" rel="alternate" type="text/html" title="A simple irc bot in ruby" /><published>2009-03-07T19:55:00+00:00</published><updated>2009-03-07T19:55:00+00:00</updated><id>/2009/03/07/simple-irc-bot-in-ruby</id><content type="html" xml:base="/2009/03/07/simple-irc-bot-in-ruby.html">&lt;p&gt;Recently it occurred to my colleagues and me that an IRC bot could benefit our chat room discussions. I looked around a bit and I couldn&apos;t find any simple ruby IRC bots to use. However, in my searching, I did find shout-bot. &lt;a href=&quot;http://github.com/sr/shout-bot&quot;&gt;Shout-bot&lt;/a&gt; is a simple IRC &quot;shouter&quot; in that it connects, reports a message, and then disconnects. Using this as a starting point, I was able to create a simple bot that stays connected to a room and responds to messages.
&lt;/p&gt;

&lt;p&gt;This bot isn&apos;t very complex and is far from perfect, but it is a good starting point if you want an irc bot that responds to just a few things. You can also find this at its &lt;a href=&quot;http://github.com/kjg/simpleircbot&quot;&gt;github repository&lt;/a&gt;.
&lt;/p&gt;

&lt;pre&gt;
#!/usr/bin/env ruby

require &apos;socket&apos;

class SimpleIrcBot

  def initialize(server, port, channel)
    @channel = channel
    @socket = TCPSocket.open(server, port)
    say &quot;NICK IrcBot&quot;
    say &quot;USER ircbot 0 * IrcBot&quot;
    say &quot;JOIN ##{@channel}&quot;
    say_to_chan &quot;#{1.chr}ACTION is here to help#{1.chr}&quot;
  end

  def say(msg)
    puts msg
    @socket.puts msg
  end

  def say_to_chan(msg)
    say &quot;PRIVMSG ##{@channel} :#{msg}&quot;
  end

  def run
    until @socket.eof? do
      msg = @socket.gets
      puts msg

      if msg.match(/^PING :(.*)$/)
        say &quot;PONG #{$~[1]}&quot;
        next
      end

      if msg.match(/PRIVMSG ##{@channel} :(.*)$/)
        content = $~[1]

        #put matchers here
        if content.match()
          say_to_chan(&apos;your response&apos;)
        end
      end
    end
  end

  def quit
    say &quot;PART ##{@channel} :Daisy, Daisy, give me your answer do&quot;
    say &apos;QUIT&apos;
  end
end

bot = SimpleIrcBot.new(&quot;irc.freenode.net&quot;, 6667, &apos;ChannelName&apos;)

trap(&quot;INT&quot;){ bot.quit }

bot.run
&lt;/pre&gt;</content><author><name>Kevin</name></author><summary type="html">Recently it occurred to my colleagues and me that an IRC bot could benefit our chat room discussions. I looked around a bit and I couldn&apos;t find any simple ruby IRC bots to use. However, in my searching, I did find shout-bot. Shout-bot is a simple IRC &quot;shouter&quot; in that it connects, reports a message, and then disconnects. Using this as a starting point, I was able to create a simple bot that stays connected to a room and responds to messages.</summary></entry><entry><title type="html">Coda Plug-in: Remove Trailing Whitespace</title><link href="/2008/12/10/coda-plugin-remove-trailing-whitespace.html" rel="alternate" type="text/html" title="Coda Plug-in: Remove Trailing Whitespace" /><published>2008-12-10T05:30:00+00:00</published><updated>2008-12-10T05:30:00+00:00</updated><id>/2008/12/10/coda-plugin-remove-trailing-whitespace</id><content type="html" xml:base="/2008/12/10/coda-plugin-remove-trailing-whitespace.html">&lt;p&gt;&lt;strong&gt;Edit:&lt;/strong&gt; &lt;em&gt;Erik Hinterbichler has written a much better native plugin called &lt;a href=&quot;http://erikhinterbichler.com/software/white-out/&quot;&gt;White Out&lt;/a&gt;. I highly recommend using that one instead of this one.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I&apos;ve been using &lt;a href=&quot;http://www.panic.com/coda/&quot;&gt;Coda&lt;/a&gt; to do all of my programming for quite some time now. One thing I wish it had, though, is the ability to remove trailing white space from lines when the file is saved.&lt;/p&gt;

&lt;p&gt;With Coda 1.6 they added the ability to create &lt;a href=&quot;http://www.panic.com/coda/developer/howto/plugins.php&quot;&gt;plug-ins&lt;/a&gt;, and with the release of 1.6.1 a few days ago they have added the ability to manipulate the whole document with plug-ins. After hearing this I decided to write my own to take care of that pesky trailing white space issue. Coda doesn&apos;t have the ability to have plug-ins run upon file save, but it&apos;s the best I could do with what they provide and what I know how to do.&lt;/p&gt;

&lt;p&gt;You can download the plug-in here:&lt;/p&gt;

&lt;p style=&quot;text-align: center;&quot;&gt;
&lt;a href=&quot;http://glowacz.info/remove_trailing_whitespace_1.0.zip&quot; style=&quot;text-decoration: none;&quot;&gt;&lt;img src=&quot;http://glowacz.info/coda_plugin.png&quot; /&gt;&lt;/a&gt;
&lt;br/&gt;&lt;a href=&quot;http://glowacz.info/remove_trailing_whitespace_1.0.zip&quot;&gt;Remove Trailing Whitespace v1.0&lt;/a&gt;
&lt;/p&gt;

&lt;p&gt;Here is the code I used to write it. The $$IP$$ stuff is so that the insertion point remains where it was before the code is run. If I leave it out, the insertion point goes to the end of the file. You can also visit its &lt;a href=&quot;http://github.com/kjg/remove_trailing_whitespace&quot;&gt;github repository&lt;/a&gt;.&lt;/p&gt;

&lt;pre&gt;
#!/usr/bin/ruby

ip_line = ENV[&apos;CODA_LINE_NUMBER&apos;].to_i
ip_index = ENV[&apos;CODA_LINE_INDEX&apos;].to_i

$stdin.each_line do |line|
  line.rstrip!

  if $stdin.lineno == ip_line
    ip_index = line.length if (line.length) &amp;lt; ip_index
    line = line.insert(ip_index, &quot;$$IP$$&quot;)
  end

  print line
  print ENV[&apos;CODA_LINE_ENDING&apos;]
end
&lt;/pre&gt;</content><author><name>Kevin</name></author><summary type="html">Edit: Erik Hinterbichler has written a much better native plugin called White Out. I highly recommend using that one instead of this one.</summary></entry><entry><title type="html">Dynamic Delicious Share Link for Blogger</title><link href="/2008/11/20/dynamic-delicious-share-link-for.html" rel="alternate" type="text/html" title="Dynamic Delicious Share Link for Blogger" /><published>2008-11-20T04:40:00+00:00</published><updated>2008-11-20T04:40:00+00:00</updated><id>/2008/11/20/dynamic-delicious-share-link-for</id><content type="html" xml:base="/2008/11/20/dynamic-delicious-share-link-for.html">&lt;p&gt;I was working on adding share links to my blog posts and I discovered that both &lt;a href=&quot;http://digg.com/tools/integrate&quot;&gt;Digg&lt;/a&gt; and &lt;a href=&quot;http://www.reddit.com/buttons&quot;&gt;reddit&lt;/a&gt; have pretty nice dynamic share buttons with instruction on how to use them. These buttons show the number of diggs or up-votes obtained by the article if they have already been submitted.&lt;/p&gt;

&lt;p&gt;While &lt;a href=&quot;http://delicious.com/help/savebuttons&quot;&gt;delicious&lt;/a&gt; also has some nice buttons, the one that shows the number of times an article has been bookmarked does not allow for a url to be specified. It only works for the location of the page it is on. This means I can&apos;t put it at the bottom of each post and have it work when multiple posts are displayed.&lt;/p&gt;

&lt;p&gt;After looking around, I found a &lt;a href=&quot;http://woork.blogspot.com/&quot;&gt;site&lt;/a&gt; that uses the Delicious API to grab the total post count. The code he uses though is not very optimized, so I wrote my own. He was also using his own version of the Delicious button and is slightly outdated. This following code uses the button from the Delicious site that pops up a javascript window and includes my code to add the bookmark count next to the link.&lt;/p&gt;

&lt;p&gt;In your blogger template, place the following code somewhere inside the head tag:&lt;/p&gt;

&lt;pre&gt;
&amp;lt;script type=&apos;text/javascript&apos;&amp;gt;
  function print_delicious_data(data){
    var urlinfo = data[0] || {};
    if (!urlinfo.total_posts) return;
    document.write(urlinfo.total_posts + &quot; save&quot;);
    if(urlinfo.total_posts!=1) document.write(&quot;s&quot;);
  }
&amp;lt;/script&amp;gt;
&lt;/pre&gt;

&lt;p&gt;Put the rest inside &amp;lt;div class=&quot;post-footer&quot;&amp;gt;&lt;/p&gt;

&lt;pre&gt;
&amp;lt;a expr:href=&apos;&quot;http://delicious.com/save?url=&quot;+data:post.url + &quot;&amp;amp;amp;title=&quot; + data:post.title&apos;
   expr:onclick=&apos;
    &quot;window.open(
      \&quot;http://delicious.com/save?v=5&amp;amp;amp;noui&amp;amp;amp;jump=close&amp;amp;amp;url=\&quot; +
        encodeURIComponent(\&quot;&quot; + data:post.url + &quot;\&quot;) + \&quot;&amp;amp;amp;title=\&quot; +
        encodeURIComponent(\&quot;&quot; + data:post.title + &quot;\&quot;),
        \&quot;delicious\&quot;,\&quot;toolbar=no,width=550,height=550\&quot;
    ); return false;&quot;&apos;
&amp;gt;Bookmark this on Delicious&amp;lt;/a&amp;gt;


&amp;lt;script expr:src=&apos;&quot;http://feeds.delicious.com/v2/json/urlinfo?url=&quot; +
data:post.url + &quot;&amp;amp;amp;callback=print_delicious_data&quot;&apos;/&amp;gt;

&lt;/pre&gt;

&lt;p&gt;Just for fun, here is the code I use for the Digg and reddit buttons.&lt;/p&gt;

&lt;pre&gt;
&amp;lt;script type=&apos;text/javascript&apos;&amp;gt;
  digg_url = &apos;&amp;lt;data:post.url/&amp;gt;&apos;;
  digg_title = &apos;&amp;lt;data:post.title/&amp;gt;&apos;;
  digg_topic = &apos;programming&apos;;
  digg_skin = &apos;compact&apos;;
  digg_window = &apos;new&apos;;
&amp;lt;/script&amp;gt;
&amp;lt;script src=&apos;http://digg.com/tools/diggthis.js&apos; type=&apos;text/javascript&apos;/&amp;gt;

&amp;lt;script type=&apos;text/javascript&apos;&amp;gt;
  reddit_url=&apos;&amp;lt;data:post.url/&amp;gt;&apos;;
  reddit_title=&apos;&amp;lt;data:post.title/&amp;gt;&apos;;
  reddit_newwindow=&apos;1&apos;;
&amp;lt;/script&gt;
&amp;lt;script src=&apos;http://www.reddit.com/button.js?t=1&apos; type=&apos;text/javascript&apos;/&amp;gt;
&lt;/pre&gt;</content><author><name>Kevin</name></author><summary type="html">I was working on adding share links to my blog posts and I discovered that both Digg and reddit have pretty nice dynamic share buttons with instruction on how to use them. These buttons show the number of diggs or up-votes obtained by the article if they have already been submitted.</summary></entry><entry><title type="html">Multiple asynchronous jQuery forms OR Making jQuery objects work for you</title><link href="/2008/11/13/multiple-asynchronous-jquery-forms-or.html" rel="alternate" type="text/html" title="Multiple asynchronous jQuery forms OR Making jQuery objects work for you" /><published>2008-11-13T17:30:00+00:00</published><updated>2008-11-13T17:30:00+00:00</updated><id>/2008/11/13/multiple-asynchronous-jquery-forms-or</id><content type="html" xml:base="/2008/11/13/multiple-asynchronous-jquery-forms-or.html">&lt;p&gt;There are times when I find the need to put multiple forms on a page. Lets say for example I have a couple of surveys for my users. I want them to be able to participate in all of the surveys, but I don’t want to make them hit the back button to fill out the subsequent forms. This, of course, can be done pretty easily with with ajax. Using both &lt;a href=&quot;http://jquery.com/&quot;&gt;jQuery&lt;/a&gt; and the &lt;a href=&quot;http://www.malsup.com/jquery/form/&quot;&gt;Forms Plugin&lt;/a&gt;, this is just a single line of javascript.&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nf&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;form&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;ajaxForm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;That was easy enough. All my forms will submit via ajax now. While this will work, it isn’t quite enough. Users like feedback, and right now they wont even know that anything happened.&lt;/p&gt;

&lt;p&gt;I like to use a submit button (html button as opposed to input type=”submit”) that has an image that changes to a spinner when the form is submitted. I also disable the buttons while the form is submitting. (There is a &lt;a href=&quot;http://particletree.com/features/rediscovering-the-button-element/&quot;&gt;nice tutorial&lt;/a&gt; on styling buttons this way.)&lt;/p&gt;

&lt;p&gt;The forms plugin gives us a nice &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;beforeSubmit()&lt;/code&gt; function. It is similar to jQuery’s ajax &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;beforeSend()&lt;/code&gt;, but provides different input parameters. I will take advantage of the second parameter here – the form object. This will swap out the submit button’s image with a spinner image and disable the buttons before the XHR request is sent.&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;spinner&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Image&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;spinner&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;src&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/path/to/spinner.gif&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;nf&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;form&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;ajaxForm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;beforeSubmit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;$form&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;opts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;){&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;$form&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;find&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;button&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;attr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;disabled&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;disabled&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;$form&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;find&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;button img&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;replaceWith&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;spinner&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;clone&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;());&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Okay, great. We don’t want to spin forever though, so we should change the images back when the request is done processing. I reset my buttons in the complete callback. You can use the success callback if you’d like, but if the form submission returns an error code then your button will be spinning and disabled forever.&lt;/p&gt;

&lt;p&gt;The problem with both the ‘complete’ and the ‘success’ callbacks is they don’t give you the form object like the beforeSubmit callback does. If you just have one form on the page this isn’t really a big issue because you can just use jQuery to select the form again via &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;$(&apos;form&apos;)&lt;/code&gt; within the callback. I have multiple forms on my page though. If the second form is submitted before the first form completes, then both forms will be reset when the first one finishes.&lt;/p&gt;

&lt;p&gt;I need a way to know which form submission is completing inside the callback. I can do this by storing the form in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jQuery.ajax()&lt;/code&gt; options object. Inside any of the callback functions ‘this’ refers to the options object, so it actually quite convenient.&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;spinner&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Image&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;spinner&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;src&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;/path/to/spinner.gif&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;nf&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;form&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;ajaxForm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;beforeSubmit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;$form&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;opts&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;){&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;$form&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;$form&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;$buttonImage&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;$form&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;find&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;button img&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;$form&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;find&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;button&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;attr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;disabled&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;disabled&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;find&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;img&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;replaceWith&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;spinner&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;clone&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;complete&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(){&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;$form&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;find&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;button&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;removeAttr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;disabled&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;find&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;img&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;replaceWith&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;$buttonImage&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;I do think it is a little shady to be modifying jQuery objects. If for some reason they decide to add a $form property in the future and I upgrade my jQuery then obviously my code will overwrite that property. I think the benefits outweigh the risk though.&lt;/p&gt;

&lt;p&gt;In my fully tricked out forms, I’ll even add my own functions to the object to make things easier. Here is code for some forms that have both a save and cancel button and has server side validations. Fields that are invalid are passed back in the response. This will also display both success and error messages after submission. I have our server generate a 422 (Unprocessable Entity) on invalid forms. jQuery doesn’t automatically extract the response data for in the error call back, but it provides the XHR object so it is pretty easy to extract it myself.&lt;/p&gt;

&lt;div class=&quot;language-js highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nf&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;form&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;ajaxForm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;({&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;resetForm&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;beforeSubmit&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;$form&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;){&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;$form&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;$form&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;$saveButton&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;$form&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;find&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;button.save&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;$saveButtonImage&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;$saveButton&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;find&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;img&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;$saveButton&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;attr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;disabled&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;disabled&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;find&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;img&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;replaceWith&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;loadingImage&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;clone&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;());&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;clearMessages&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(){&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;$form&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;find&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;.invalid&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;andSelf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;removeClass&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;invalid&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;$form&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;find&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;.errors, .success&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;remove&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;complete&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(){&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;$saveButton&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;removeAttr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;disabled&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;find&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;img&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;replaceWith&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;$saveButtonImage&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;success&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(){&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;clearMessages&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;$form&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;prepend&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;amp;lt;div class=&quot;success&quot;&amp;amp;gt;&amp;amp;lt;p class=&quot;heading&quot;&amp;amp;gt;Your submission has been recorded.&amp;amp;lt;/p&amp;amp;gt;&amp;amp;lt;/div&amp;amp;gt;&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;$form&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;find&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;.success&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;scrollIntoView&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;error&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;XHR&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;){&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;clearMessages&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;$form&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;$form&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;XHR&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;status&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;422&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;){&lt;/span&gt;
      &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;eval&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;XHR&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;responseText&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

      &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;typeof&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;invalid_fields&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;undefined&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;error_box&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;amp;lt;div class=&quot;errors&quot;&amp;amp;gt;&amp;amp;lt;p class=&quot;heading&quot;&amp;amp;gt;There were errors processing your submission.&amp;amp;lt;/p&amp;amp;gt;&amp;amp;lt;ol&amp;amp;gt;&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

        &lt;span class=&quot;nx&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;each&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;invalid_fields&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;errors&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;){&lt;/span&gt;
          &lt;span class=&quot;nx&quot;&gt;$form&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;find&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;.form_field_&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;addClass&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;invalid&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
          &lt;span class=&quot;nx&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;each&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;errors&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(){&lt;/span&gt;
            &lt;span class=&quot;nx&quot;&gt;error_box&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;amp;lt;li&amp;amp;gt;&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;amp;lt;/li&amp;amp;gt;&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
          &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;

        &lt;span class=&quot;nx&quot;&gt;error_box&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+=&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&amp;amp;lt;/ol&amp;amp;gt;&amp;amp;lt;/div&amp;amp;gt;&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;$form&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;prepend&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;error_box&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;nx&quot;&gt;$form&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;find&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;.errors&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;scrollIntoView&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
      &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;</content><author><name>Kevin</name></author><category term="jQuery" /><category term="Forms" /><summary type="html">There are times when I find the need to put multiple forms on a page. Lets say for example I have a couple of surveys for my users. I want them to be able to participate in all of the surveys, but I don’t want to make them hit the back button to fill out the subsequent forms. This, of course, can be done pretty easily with with ajax. Using both jQuery and the Forms Plugin, this is just a single line of javascript.</summary></entry></feed>