theoleschool - drupal http://theoleschool.com/tags/drupal en Using Drupal Content Channel Tokens with Nodejs http://theoleschool.com/blog/using-drupal-content-channel-tokens-nodejs <div class="field field-name-body field-type-text-with-summary field-label-hidden"><div class="field-items"><div class="field-item even" property="content:encoded"><p>You've decided to add a bit of Node.js functionality to your Drupal site using the <a href="https://drupal.org/project/nodejs">Drupal Nodejs Module</a>. After <a href="http://theoleschool.com/blog/module-development-nodejs-integration">some reading</a> you realize you need to be setting up user message channels using <span class="geshifilter"><code class="php geshifilter-php">hook_nodejs_user_channels</code></span> or possibly <span class="geshifilter"><code class="php geshifilter-php">nodejs_add_user_to_channel</code></span>. This allows your application to send socket messages to groups of users. Great! But you're writing no simple application. You want to go one step further and manage these channels based not only on some property of the user object, but all users who may be currently viewing a particular Drupal served page. Suddenly the typical user channels no longer cut it. Try as you might, you'll find yourself quickly descending into channel management hell. There has got to be a better way. Thankfully content token channels are here to help.</p> <h3>Send a Message Based on the Content Being Viewed</h3> <p>You can think of content token channels as a per page (or piece of content) channel subscription. This differs from the user channels in that it targets users who are currently viewing a specific page rather than users targeted by some property of the user object (regardless of where they are on the site).</p> <p>There are two main PHP functions to implement a token channel. <span class="geshifilter"><code class="php geshifilter-php">nodejs_send_content_channel_token</code></span> to generate a token channel for a given piece of content, and <span class="geshifilter"><code class="php geshifilter-php">nodejs_send_content_channel_message</code></span> to send a message to users subscribed to a given content channel.</p> <p>To illustrate this lets assume we want to create a channel to send user messages when they are viewing a Drupal node of type <em>page</em>. To begin we will need to setup our content channel for that node type. A good place to do this would be <span class="geshifilter"><code class="php geshifilter-php">hook_node_view</code></span> which fires when a user views a node.</p> <div class="geshifilter"><div class="php geshifilter-php" style="font-family:monospace;"><span style="color: #000000; font-weight: bold;">function</span> example_node_view<span style="color: #009900;">&#40;</span><span style="color: #000088;">$node</span><span style="color: #339933;">,</span> <span style="color: #000088;">$view_mode</span><span style="color: #339933;">,</span> <span style="color: #000088;">$langcode</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span><br /> &nbsp; <span style="color: #b1b100;">if</span><span style="color: #009900;">&#40;</span><span style="color: #000088;">$node</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">type</span> <span style="color: #339933;">==</span> <span style="color: #0000ff;">&quot;page&quot;</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span><br /> &nbsp; &nbsp; nodejs_send_content_channel_token<span style="color: #009900;">&#40;</span><span style="color: #0000ff;">'page_node_channel'</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span><br /> &nbsp; <span style="color: #009900;">&#125;</span><br /> <span style="color: #009900;">&#125;</span></div></div> <p>Now every time a node of type <em>page</em> is viewed the Node.js server is notified of the <em>page&#95;node&#95;channel</em> content channel. Node.js then adds the channel to the user for whom the page was rendered.</p> <p>From here if we wish to send a message to all of the users viewing a node of type <em>page</em> we make a simple call to <span class="geshifilter"><code class="php geshifilter-php">nodejs_send_content_channel_message</code></span>. When and where you choose to fire this message is up to you. Possibly you want to notify users if the page they are viewing has been updated (<span class="geshifilter"><code class="php geshifilter-php">hook_node_update</code></span>), a user logs in (<span class="geshifilter"><code class="php geshifilter-php">hook_user_login</code></span>), or some new content was added (<span class="geshifilter"><code class="php geshifilter-php">hook_node_insert</code></span>).</p> <div class="geshifilter"><div class="php geshifilter-php" style="font-family:monospace;"><span style="color: #666666; font-style: italic;">// Build a message object</span><br /> <span style="color: #000088;">$message</span> <span style="color: #339933;">=</span> <span style="color: #000000; font-weight: bold;">new</span> stdClass<span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span><br /> <span style="color: #000088;">$message</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">channel</span> <span style="color: #339933;">=</span> <span style="color: #0000ff;">'page_node_channel'</span><span style="color: #339933;">;</span><br /> <span style="color: #000088;">$message</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">data</span><span style="color: #009900;">&#91;</span><span style="color: #0000ff;">'body'</span><span style="color: #009900;">&#93;</span> <span style="color: #339933;">=</span> <span style="color: #0000ff;">'Hello World!'</span><span style="color: #339933;">;</span><br /> <br /> <span style="color: #666666; font-style: italic;">// Send the message to the channel we created</span><br /> nodejs_send_content_channel_message<span style="color: #009900;">&#40;</span><span style="color: #000088;">$message</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span></div></div> <p>And that's about it! When <span class="geshifilter"><code class="php geshifilter-php">nodejs_send_content_channel_message</code></span> fires our message will be broadcast to all users subscribed to the page_node_channel. In this case all users who are currently viewing a node of type <em>page</em>. This message behaves like any other on the Node.js side, so you could send it to a custom callback handler, as mentioned <a href="http://theoleschool.com/blog/module-development-nodejs-integration">in an earlier post</a>.</p> <h3>Token Messages From an Node.js Extension</h3> <p>If you are writing a Node.js server extension and wish to send a message to a content channel, it is worth noting that there is no immediately exposed functionality to do this. However, Node.js server extensions are aware of what the content channels are via <span class="geshifilter"><code class="php geshifilter-php">config<span style="color: #339933;">.</span>tokenChannels</code></span>. This allows us to mimic the functionality of the Drupal Node.js server with a helper method in our server extension.</p> <div class="geshifilter"><div class="jquery geshifilter-jquery" style="font-family:monospace;"><span style="color: #003366; font-weight: bold;">function</span> sendMessageToTokenChannel<span style="color: #009900;">&#40;</span>message<span style="color: #339933;">,</span> config<span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span><br /> &nbsp; <span style="color: #000066; font-weight: bold;">if</span> <span style="color: #009900;">&#40;</span><span style="color: #339933;">!</span>message.<span style="color: #660066;">hasOwnProperty</span><span style="color: #009900;">&#40;</span><span style="color: #3366CC;">'channel'</span><span style="color: #009900;">&#41;</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span><br /> &nbsp; &nbsp; console.<span style="color: #660066;">log</span><span style="color: #009900;">&#40;</span><span style="color: #3366CC;">'publishMessageToContentChannel: An invalid message object was provided.'</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span><br /> &nbsp; &nbsp; <span style="color: #000066; font-weight: bold;">return</span><span style="color: #339933;">;</span><br /> &nbsp; <span style="color: #009900;">&#125;</span><br /> &nbsp; <span style="color: #000066; font-weight: bold;">if</span> <span style="color: #009900;">&#40;</span><span style="color: #339933;">!</span>config.<span style="color: #660066;">tokenChannels</span>.<span style="color: #660066;">hasOwnProperty</span><span style="color: #009900;">&#40;</span>message.<span style="color: #660066;">channel</span><span style="color: #009900;">&#41;</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span><br /> &nbsp; &nbsp; console.<span style="color: #660066;">log</span><span style="color: #009900;">&#40;</span><span style="color: #3366CC;">'publishMessageToContentChannel: The channel &quot;'</span> <span style="color: #339933;">+</span> message.<span style="color: #660066;">channel</span> <span style="color: #339933;">+</span> <span style="color: #3366CC;">'&quot; doesn<span style="color: #000099; font-weight: bold;">\'</span>t exist.'</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span><br /> &nbsp; &nbsp; <span style="color: #000066; font-weight: bold;">return</span><span style="color: #339933;">;</span><br /> &nbsp; <span style="color: #009900;">&#125;</span><br /> <br /> &nbsp; <span style="color: #000066; font-weight: bold;">for</span> <span style="color: #009900;">&#40;</span><span style="color: #003366; font-weight: bold;">var</span> socketId <span style="color: #000066; font-weight: bold;">in</span> config.<span style="color: #660066;">tokenChannels</span><span style="color: #009900;">&#91;</span>message.<span style="color: #660066;">channel</span><span style="color: #009900;">&#93;</span>.<span style="color: #660066;">sockets</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#123;</span><br /> &nbsp; &nbsp; config.<span style="color: #660066;">publishMessageToClient</span><span style="color: #009900;">&#40;</span>socketId<span style="color: #339933;">,</span> message<span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span><br /> &nbsp; <span style="color: #009900;">&#125;</span><br /> <span style="color: #009900;">&#125;</span></div></div> <p>Adding this method to your server extension, will allow you to send messages to a content channel, by passing the message and config objects from within your <span class="geshifilter"><code class="php geshifilter-php">exports<span style="color: #339933;">.</span>setup</code></span> function.</p> <p>Using content token channels has greatly simplified several of the Drupal/Node.js projects I have done. However, it wasn't immediately apparent that this functionality existed. So I hope this helps to further clarify the capabilities of this module.</p> </div></div></div> Mon, 28 Apr 2014 17:29:06 +0000 rho 165 at http://theoleschool.com Hubot Drush me Drupal http://theoleschool.com/blog/hubot-drush-me-drupal <div class="field field-name-body field-type-text-with-summary field-label-hidden"><div class="field-items"><div class="field-item even" property="content:encoded"><p>Recently the company I work for shifted the bulk of our instant message communications over to <a href="http://campfirenow.com/">Campfire</a>, and established a series of rooms to discuss our various projects. This transition seemed to go fairly well, once everyone settled on their preferred client interface. I was used to using IRC and found a <a href="https://github.com/zerowidth/camper_van">nice little Ruby gem</a> that piped Campfire through my IRC client. Chatting in this environment quickly had me missing Druplicon, the friendly Drupal fueled chat bot that tirelessly serves the Drupal IRC community. I decided to look for a replacement, and after some searching I stumbled upon <a href="http://hubot.github.com/">Hubot</a>, a Node.js powered chat bot. It was initially built by the folks at GitHub and includes a long list of <a href="http://hubot-script-catalog.herokuapp.com/">plugin scripts</a> added by community developers. This was immediately appealing for several reasons:</p> <ul> <li>It is built using Node.js</li> <li>It is written in Coffeescript (something I have wanted to try my hand at for a while)</li> <li>Additional scripts appeared easy to write, and plugin simply</li> <li>It has built in Campfire integration</li> </ul> <p>So I installed Hubot on a small server, with a handful of scripts that I thought would be useful and fun for our team. Adding the bot to our Campfire rooms was fairly painless, and after writing a small init script we were good to go. This sparked some discussion among our team about how the bot was deployed, cool ways it could be extended, and methods of use. A fellow developer, <a href="http://davidfells.net/">David Fells</a>, mentioned that it would be handy if we could tell Hubot to execute <a href="http://drupal.org/project/drush">Drush</a> commands. This gave me pause, and after a quick:</p> <p>"That's a great idea! You mind if I implement that?"<br /> "Sure, go for it."</p> <p>I found myself firing up my editor, trying a few test methods, and reading up on <a href="http://coffeescript.org/">CoffeeScript</a>. Things moved fairly quickly, once I got going. I realized <a href="http://nodejs.org/api/child_process.html#child_process_child_process_spawn_command_args_options"><code>child_process.spawn()</code></a>was going to be my friend, a core Node method that I had yet to use. Generally CoffeeScript proved to be the biggest challenge. While writing the initial prototype I actually used a <a href="http://js2coffee.org/">JavaScript to CoffeScript translator</a> and was cursing the whole time. However, by the time I was putting the finishing touches on my methods and objects I found myself smiling as I wrote in this almost comical syntax. Simply put, CoffeeScript is fun! I still find myself favoring the comforting structure of the curly braces and semicolons when working with JavaScript, but now that all seems somewhat gray and drab, knowing there is something much more playful out there.</p> <p>As you can imagine there were, and are, several security concerns. The early prototype actually had user commands pipe right into Drush which is a security nightmare. Any user with access to the bot could escape or pipe in any old Bash command directly through the bot. Further, any Drupal installation that Drush is aware of would be at the mercy of users who may or may not have the best intentions at heart. Needless to say this approach was quickly abandoned in favor of a hand picked list of commands made available to the bot. This caused the code to be a bit more tedious, and likely less elegant, but I needed to insure that Drush would be executing the allowed commands, and nothing more. After some discussion I also decided to weed the available commands down to a relatively innocuous set (giving information vs. actually changing something). I attempted to structure the code in a way that it would not be difficult for another developer to add commands. Overall, I'm pretty happy with the <a href="https://github.com/github/hubot-scripts/blob/master/src/scripts/drush.coffee">end result</a>, and the pull request was soon accepted. You can find this script along with all of the other <a href="http://hubot-script-catalog.herokuapp.com/#drush.coffee">Hubot scripts on GitHub</a>.</p> <p><em><strong>tl;dr</strong> If you're looking for a fun chatbot for campfire (or otherwise) check out <a href="http://hubot.github.com/">Hubot</a> and enable <a href="http://hubot-script-catalog.herokuapp.com/#drush.coffee">drush.coffee</a> if you'd like that bot to have cool Drush functionality.</em></p> </div></div></div> Wed, 08 May 2013 04:15:48 +0000 rho 163 at http://theoleschool.com Talking to Drupal with Node.js http://theoleschool.com/blog/talking-drupal-nodejs <div class="field field-name-body field-type-text-with-summary field-label-hidden"><div class="field-items"><div class="field-item even" property="content:encoded"><p>As I've mentioned in a <a href="/blog/module-development-nodejs-integration">previous post</a> sending messages from Drupal to Node is fairly easy to accomplish. However, that is only part of the equation. If we're really wanting to build a larger, more complex Node application, it won't be long before we need the client to talk to the Node server and possibly the Node server to talk to Drupal. At a glance it isn't terribly obvious how to do this with the <a href="http://drupal.org/project/nodejs">nodejs integration module</a>, but it turns out there are a few tools tucked in the module code that come to the rescue.</p> <p>This post will assume some familiarity with the use of the server extension functionality built into the nodejs module. If you are unfamiliar with this, have a look at the <a href="http://drupalcode.org/project/nodejs.git/blob/4dd8f7808feb70d2f71c6978336ffeaed7eef65a:/nodejs.server.extension.js.example">example server extension</a> included with the module. In short it does what you might think, allowing other modules to extend the nodejs server setup by the nodejs module. For context I've posted a fully commented <a href="https://github.com/rh0/node_to_drupal">example module</a> to GitHub from which the code snippets of this post are taken.</p> <p>If you are familiar with writing node apps with <a href="http://socket.io/">Socket.io</a> then you are likely already familiar with client side communication back to the node server. Typically you will have a <span class="geshifilter"><code class="php geshifilter-php">socket<span style="color: #339933;">.</span>emit</code></span> on the client side, and a listener to handle the message event on the server side. The same is true when using the nodejs module. In this example I'm building a very simple form with Drupal and displaying it on a page. When the submit button is hit, a bit of client side javascript emits a message through socket.io which is in the <span class="geshifilter"><code class="php geshifilter-php">Drupal<span style="color: #339933;">.</span>Nodejs</code></span> object. </p> <div class="geshifilter"><div class="jquery geshifilter-jquery" style="font-family:monospace;"><span style="color: #006600; font-style: italic;">// A simple message.  The listener on the server side does not like us to</span><br /><span style="color: #006600; font-style: italic;">// specify 'messageType' so we're calling it 'type' for now.</span><br /><span style="color: #003366; font-weight: bold;">var</span> message <span style="color: #339933;">=</span> <span style="color: #009900;">{</span><br />   type<span style="color: #339933;">:</span> <span style="color: #3366CC;">'nodeToDrupal'</span><span style="color: #339933;">,</span><br />   messageBody<span style="color: #339933;">:</span> <span style="color: #3366CC;">'Hello from the client side!'</span><br /><span style="color: #009900;">}</span><span style="color: #339933;">;</span><br /><br /> Drupal.<span style="color: #660066;">behaviors</span>.<span style="color: #660066;">sendDrupalMessage</span> <span style="color: #339933;">=</span> <span style="color: #009900;">{</span><br />   attach<span style="color: #339933;">:</span> <span style="color: #003366; font-weight: bold;">function</span><span style="color: #009900;">(</span><a href="http://docs.jquery.com/Core/context"><span style="color: #000066;">context</span></a><span style="color: #339933;">,</span> settings<span style="color: #009900;">)</span> <span style="color: #009900;">{</span><br />     <span style="color: #006600; font-style: italic;">// Have our message emitter fire on form submission.</span><br />     <span style="color: #000066;">$</span><span style="color: #009900;">(</span><span style="color: #3366CC;">"#node-to-drupal-form"</span><span style="color: #009900;">)</span>.<a href="http://docs.jquery.com/Events/submit"><span style="color: #000066;">submit</span></a><span style="color: #009900;">(</span><span style="color: #003366; font-weight: bold;">function</span><span style="color: #009900;">(</span><span style="color: #009900;">)</span> <span style="color: #009900;">{</span><br />       Drupal.<span style="color: #660066;">Nodejs</span>.<span style="color: #660066;">socket</span>.<span style="color: #660066;">emit</span><span style="color: #009900;">(</span><span style="color: #3366CC;">'message'</span><span style="color: #339933;">,</span> message<span style="color: #009900;">)</span><span style="color: #339933;">;</span><br />       <span style="color: #000066; font-weight: bold;">return</span> <span style="color: #003366; font-weight: bold;">false</span><span style="color: #339933;">;</span><br />     <span style="color: #009900;">}</span><span style="color: #009900;">)</span><span style="color: #339933;">;</span><br />   <span style="color: #009900;">}</span><br /><span style="color: #009900;">}</span></div></div> <p>On the server side, in our server extension file, we need to catch our message. This is done in a fairly standard way, the event we are listening for is <span class="geshifilter"><code class="php geshifilter-php">client<span style="color: #339933;">-</span>message</code></span>.</p> <div class="geshifilter"><div class="jquery geshifilter-jquery" style="font-family:monospace;"> <br /> exports.<span style="color: #660066;">setup</span> <span style="color: #339933;">=</span> <span style="color: #003366; font-weight: bold;">function</span> <span style="color: #009900;">(</span>config<span style="color: #009900;">)</span> <span style="color: #009900;">{</span><br />   process.<span style="color: #660066;">on</span><span style="color: #009900;">(</span><span style="color: #3366CC;">'client-message'</span><span style="color: #339933;">,</span> <span style="color: #003366; font-weight: bold;">function</span> <span style="color: #009900;">(</span>sessionId<span style="color: #339933;">,</span> message<span style="color: #009900;">)</span> <span style="color: #009900;">{</span><br />     <span style="color: #006600; font-style: italic;">// Logging the message to the console.</span><br />     console.<span style="color: #660066;">log</span><span style="color: #009900;">(</span><span style="color: #3366CC;">'Got a message from the client.  Take a look: '</span><span style="color: #009900;">)</span><span style="color: #339933;">;</span><br />     console.<span style="color: #660066;">log</span><span style="color: #009900;">(</span>message<span style="color: #009900;">)</span><span style="color: #339933;">;</span><br />   <span style="color: #009900;">}</span><span style="color: #009900;">)</span><span style="color: #339933;">;</span><br /><span style="color: #009900;">}</span></div></div> <p>If a client message comes through, we log it to the console. For a lot of node applications our worries are over. We have a way for the client to send a message to the node server. From there we can have the node server do whatever we would like with it. Maybe spit a message back out to the client, to a different session, to a Redis store, or any number of other things. Now the big question. What if we want the node server to send a message to Drupal? There are several reasons we may want to do this, but chief among them would be a database write. The idea of having node write directly to the Drupal database, while possible, would surely harm large baskets full of kittens. It's a bad idea. So we need node to tell Drupal to do its bidding, and for that we need to look at a <span class="geshifilter"><code class="php geshifilter-php">hook_nodejs_message_callback</code></span>. This hook allows us to assign handler functions to various message types that being emitted from the node server, and is wonderfully simple.</p> <div class="geshifilter"><div class="drupal6 geshifilter-drupal6" style="font-family:monospace;"><span style="color: #000000; font-weight: bold;">function</span> node_to_drupal_nodejs_message_callback<span style="color: #66cc66;">(</span><span style="color: #0000ff;">$type</span><span style="color: #66cc66;">)</span> <span style="color: #66cc66;">{</span><br />   <span style="color: #b1b100;">switch</span><span style="color: #66cc66;">(</span><span style="color: #0000ff;">$type</span><span style="color: #66cc66;">)</span> <span style="color: #66cc66;">{</span><br />     <span style="color: #b1b100;">case</span> <span style="color: #ff0000;">'nodeToDrupal'</span>:<br />       <span style="color: #b1b100;">return</span> <a href="http://www.php.net/array"><span style="color: #000066;">array</span></a><span style="color: #66cc66;">(</span><span style="color: #ff0000;">'node_to_drupal_handler'</span><span style="color: #66cc66;">)</span>;<br />   <span style="color: #66cc66;">}</span><br />   <span style="color: #b1b100;">return</span> <span style="color: #000000; font-weight: bold;">false</span>;<br /><span style="color: #66cc66;">}</span></div></div> <p>The hook is fired on an <span class="geshifilter"><code class="php geshifilter-php">sendMessageToBackend</code></span> call from the node server and passes in <span class="geshifilter"><code class="php geshifilter-php">messageType</code></span> from the message. In our case the type is <span class="geshifilter"><code class="php geshifilter-php">nodeToDrupal</code></span> (more on this in a bit). The hook is expected to return and array of function names, that will subsequently be called. In this case <span class="geshifilter"><code class="php geshifilter-php">node_to_drupal_handler</code></span>.</p> <div class="geshifilter"><div class="drupal6 geshifilter-drupal6" style="font-family:monospace;"><span style="color: #000000; font-weight: bold;">function</span> node_to_drupal_handler<span style="color: #66cc66;">(</span><span style="color: #0000ff;">$message</span>, <span style="color: #66cc66;">&amp;</span><span style="color: #0000ff;">$responce</span><span style="color: #66cc66;">)</span> <span style="color: #66cc66;">{</span><br />   <span style="color: #808080; font-style: italic;">// Grab our messageBody from the client message and post it to the watchdog.</span><br />   <a href="http://api.drupal.org/api/function/watchdog/6"><span style="color: #000066;">watchdog</span></a><span style="color: #66cc66;">(</span><span style="color: #ff0000;">'node_to_drupal'</span>, <span style="color: #0000ff;">$message</span><span style="color: #66cc66;">[</span><span style="color: #ff0000;">'messageBody'</span><span style="color: #66cc66;">]</span>, <a href="http://www.php.net/array"><span style="color: #000066;">array</span></a><span style="color: #66cc66;">(</span><span style="color: #66cc66;">)</span>, <a href="http://api.drupal.org/api/constant/WATCHDOG_INFO/6"><span style="color: #000000; font-weight: bold;">WATCHDOG_INFO</span></a><span style="color: #66cc66;">)</span>;<br />   <span style="color: #808080; font-style: italic;">// Tell node we got the message.</span><br />   <span style="color: #0000ff;">$responce</span> = <span style="color: #ff0000;">'Message logged to Watchdog!'</span>;<br /><span style="color: #66cc66;">}</span></div></div> <p>The handler functions called from <span class="geshifilter"><code class="php geshifilter-php">hook_nodejs_message_callback</code></span> expect two parameters. The message from node (as an array), and a response passed by reference. In this example we're make a simple watchdog entry of the <span class="geshifilter"><code class="php geshifilter-php">messageBody</code></span>, and adding a string to the response stating that Drupal got the message.</p> <p>Going back to our node server extension, we're going to fire the message we got from the client over to Drupal. To do this were going to use a handy function in our config variable called <span class="geshifilter"><code class="php geshifilter-php">sendMessageToBackend</code></span>. Our server extension code is now looking like: </p><div class="geshifilter"><div class="jquery geshifilter-jquery" style="font-family:monospace;">exports.<span style="color: #660066;">setup</span> <span style="color: #339933;">=</span> <span style="color: #003366; font-weight: bold;">function</span> <span style="color: #009900;">(</span>config<span style="color: #009900;">)</span> <span style="color: #009900;">{</span><br />   process.<span style="color: #660066;">on</span><span style="color: #009900;">(</span><span style="color: #3366CC;">'client-message'</span><span style="color: #339933;">,</span> <span style="color: #003366; font-weight: bold;">function</span> <span style="color: #009900;">(</span>sessionId<span style="color: #339933;">,</span> message<span style="color: #009900;">)</span> <span style="color: #009900;">{</span><br />     <span style="color: #006600; font-style: italic;">// Logging the message to the console.</span><br />     console.<span style="color: #660066;">log</span><span style="color: #009900;">(</span><span style="color: #3366CC;">'Got a message from the client.  Take a look: '</span><span style="color: #009900;">)</span><span style="color: #339933;">;</span><br />     console.<span style="color: #660066;">log</span><span style="color: #009900;">(</span>message<span style="color: #009900;">)</span><span style="color: #339933;">;</span><br /><br />     <span style="color: #006600; font-style: italic;">// Insuring that messageType is set before passing along.</span><br />     message.<span style="color: #660066;">messageType</span> <span style="color: #339933;">=</span> message.<span style="color: #660066;">type</span><span style="color: #339933;">;</span><br />     <span style="color: #006600; font-style: italic;">// Send the message to Drupal.</span><br />     config.<span style="color: #660066;">sendMessageToBackend</span><span style="color: #009900;">(</span>message<span style="color: #339933;">,</span> <span style="color: #003366; font-weight: bold;">function</span><span style="color: #009900;">(</span><a href="http://docs.jquery.com/Events/error"><span style="color: #000066;">error</span></a><span style="color: #339933;">,</span> responce<span style="color: #339933;">,</span> body<span style="color: #009900;">)</span> <span style="color: #009900;">{</span><br />       <span style="color: #000066; font-weight: bold;">if</span><span style="color: #009900;">(</span><a href="http://docs.jquery.com/Events/error"><span style="color: #000066;">error</span></a><span style="color: #009900;">)</span> <span style="color: #009900;">{</span><br />         console.<span style="color: #660066;">log</span><span style="color: #009900;">(</span><span style="color: #3366CC;">'Error sending message to backend.'</span><span style="color: #339933;">,</span> <a href="http://docs.jquery.com/Events/error"><span style="color: #000066;">error</span></a><span style="color: #009900;">)</span><span style="color: #339933;">;</span><br />         <span style="color: #000066; font-weight: bold;">return</span><span style="color: #339933;">;</span><br />       <span style="color: #009900;">}</span><br />       <span style="color: #006600; font-style: italic;">// Drupal got it! Output the responce.</span><br />       console.<span style="color: #660066;">log</span><span style="color: #009900;">(</span><span style="color: #3366CC;">'Response from drupal: '</span><span style="color: #339933;">,</span> body<span style="color: #009900;">)</span><span style="color: #339933;">;</span><br />     <span style="color: #009900;">}</span><span style="color: #009900;">)</span><span style="color: #339933;">;</span><br />   <span style="color: #009900;">}</span><span style="color: #009900;">)</span><br /><span style="color: #009900;">}</span><span style="color: #339933;">;</span></div></div> <p>We grab our message from the client and send it to the Drupal backend, then upon success log Drupal's response to the console. Now our little message chain is complete! We have a message coming from the client side to the node server upon a user action. Then node passes that message along to Drupal which records it in the watchdog. This means that not only can we have our node server interacting back and forth with the client, but also have tremendous flexibility over how the node server can interact with Drupal.</p></div></div></div> Mon, 29 Oct 2012 01:36:31 +0000 rho 162 at http://theoleschool.com Templating the Commerce Order Completion Pane http://theoleschool.com/blog/templating-commerce-order-completion-pane <div class="field field-name-body field-type-text-with-summary field-label-hidden"><div class="field-items"><div class="field-item even" property="content:encoded"><p>I recently found myself working on a fairly simple Drupal Commerce site. There weren't a lot of tricks to it, just a few items and an "express" checkout. So I fired up the <a href="http://drupal.org/project/commerce_kickstart">Commerce Kickstart</a> profile and after a few small tweaks, had it just about right. As we approached the theme, it became obvious that the commerce completion pane was going to take a lot of love. The designers had given us a unique layout for how this page should appear, complete with order specific information displayed in a kaleidoscope of containers. This was going to take some markup, and my first thought was, "it sure would be nice to template this."</p><!--break--> <p>Turns out that's a fairly simple task thanks to <a href="http://api.drupalcommerce.org/api/Drupal%20Commerce/sites!all!modules!commerce!modules!checkout!commerce_checkout.api.php/function/hook_commerce_checkout_pane_info_alter/DC">hook_commerce_checkout_pane_info_alter</a>. The function passes the variable <span class="geshifilter"><code class="php geshifilter-php"><span style="color: #000088;">$checkout_panes</span></code></span> by reference which contains an array of checkout pane arrays. This function is going to allow us to redefine the function that the checkout completion pane is looking to for output. This will look something like:</p> <div class="geshifilter"><div class="drupal6 geshifilter-drupal6" style="font-family:monospace;"><span style="color: #000000; font-weight: bold;">function</span> my_module_commerce_checkout_pane_info_alter<span style="color: #66cc66;">(</span><span style="color: #66cc66;">&amp;</span><span style="color: #0000ff;">$checkout_panes</span><span style="color: #66cc66;">)</span> <span style="color: #66cc66;">{</span><br />   <span style="color: #b1b100;">if</span><span style="color: #66cc66;">(</span><a href="http://www.php.net/isset"><span style="color: #000066;">isset</span></a><span style="color: #66cc66;">(</span><span style="color: #0000ff;">$checkout_panes</span><span style="color: #66cc66;">[</span><span style="color: #ff0000;">'checkout_completion_message'</span><span style="color: #66cc66;">]</span><span style="color: #66cc66;">)</span><span style="color: #66cc66;">)</span> <span style="color: #66cc66;">{</span><br />     <span style="color: #808080; font-style: italic;">// forget the original include file                     </span><br />     <a href="http://www.php.net/unset"><span style="color: #000066;">unset</span></a><span style="color: #66cc66;">(</span><span style="color: #0000ff;">$checkout_panes</span><span style="color: #66cc66;">[</span><span style="color: #ff0000;">'checkout_completion_message'</span><span style="color: #66cc66;">]</span><span style="color: #66cc66;">[</span><span style="color: #ff0000;">'file'</span><span style="color: #66cc66;">]</span><span style="color: #66cc66;">)</span>;<br />     <span style="color: #808080; font-style: italic;">// point at this module</span><br />     <span style="color: #0000ff;">$checkout_panes</span><span style="color: #66cc66;">[</span><span style="color: #ff0000;">'checkout_completion_message'</span><span style="color: #66cc66;">]</span><span style="color: #66cc66;">[</span><span style="color: #ff0000;">'module'</span><span style="color: #66cc66;">]</span> = <span style="color: #ff0000;">'my_module'</span>;<br />     <span style="color: #808080; font-style: italic;">// register the new output function</span><br />     <span style="color: #0000ff;">$checkout_panes</span><span style="color: #66cc66;">[</span><span style="color: #ff0000;">'checkout_completion_message'</span><span style="color: #66cc66;">]</span><span style="color: #66cc66;">[</span><span style="color: #ff0000;">'base'</span><span style="color: #66cc66;">]</span> = <span style="color: #ff0000;">'my_module_completion_message_pane'</span>;<br />   <span style="color: #66cc66;">}</span><br /><span style="color: #66cc66;">}</span></div></div> <p>The function name we passed above will have <span class="geshifilter"><code class="php geshifilter-php">_checkout_form</code></span> appended. We will need to declare a function to satisfy this and return our output.</p> <div class="geshifilter"><div class="drupal6 geshifilter-drupal6" style="font-family:monospace;"><span style="color: #000000; font-weight: bold;">function</span> my_module_completion_message_pane_checkout_form<span style="color: #66cc66;">(</span><span style="color: #0000ff;">$form</span>, <span style="color: #66cc66;">&amp;</span><span style="color: #0000ff;">$form_state</span>, <span style="color: #0000ff;">$checkout_pane</span>, <span style="color: #0000ff;">$order</span><span style="color: #66cc66;">)</span> <span style="color: #66cc66;">{</span><br />   <span style="color: #0000ff;">$pane_form</span> = <a href="http://www.php.net/array"><span style="color: #000066;">array</span></a><span style="color: #66cc66;">(</span><span style="color: #66cc66;">)</span>;<br />   <span style="color: #0000ff;">$pane_form</span><span style="color: #66cc66;">[</span><span style="color: #ff0000;">'message'</span><span style="color: #66cc66;">]</span> = <a href="http://www.php.net/array"><span style="color: #000066;">array</span></a><span style="color: #66cc66;">(</span><span style="color: #ff0000;">'#markup'</span> =<span style="color: #66cc66;">&gt;</span> <span style="color: #808080; font-style: italic;">/*markup*/</span><span style="color: #66cc66;">)</span>;<br />   <span style="color: #b1b100;">return</span> <span style="color: #0000ff;">$pane_form</span>;<br /><span style="color: #66cc66;">}</span></div></div> <p>From here, we're basically home free. We simply need to replace <span class="geshifilter"><code class="php geshifilter-php"><span style="color: #666666; font-style: italic;">/*markup*/</span></code></span> with the output we desire. Since we are looking to template this, we will need to register a theme implementation for it. The important thing to note is the <span class="geshifilter"><code class="php geshifilter-php"><span style="color: #000088;">$order</span></code></span> variable passed to this function. We'll need that order information in our template, so we pass it along while laying out our <span class="geshifilter"><code class="php geshifilter-php">hook_theme</code></span> call.</p> <div class="geshifilter"><div class="drupal6 geshifilter-drupal6" style="font-family:monospace;"><span style="color: #000000; font-weight: bold;">function</span> my_module_theme<span style="color: #66cc66;">(</span><span style="color: #0000ff;">$existing</span>, <span style="color: #0000ff;">$type</span>, <span style="color: #0000ff;">$theme</span>, <span style="color: #0000ff;">$path</span><span style="color: #66cc66;">)</span> <span style="color: #66cc66;">{</span><br />   <span style="color: #b1b100;">return</span> <a href="http://www.php.net/array"><span style="color: #000066;">array</span></a><span style="color: #66cc66;">(</span><br />     <span style="color: #ff0000;">'my_module_completion'</span> =<span style="color: #66cc66;">&gt;</span> <a href="http://www.php.net/array"><span style="color: #000066;">array</span></a><span style="color: #66cc66;">(</span><br />       <span style="color: #808080; font-style: italic;">// the template filename, .tpl.php will be appended</span><br />       <span style="color: #ff0000;">'template'</span> =<span style="color: #66cc66;">&gt;</span> <span style="color: #ff0000;">'my_module_completion'</span>,<br />       <span style="color: #808080; font-style: italic;">// include order as a variable in the template</span><br />       <span style="color: #ff0000;">'variables'</span> =<span style="color: #66cc66;">&gt;</span> <a href="http://www.php.net/array"><span style="color: #000066;">array</span></a><span style="color: #66cc66;">(</span><span style="color: #ff0000;">'order'</span> =<span style="color: #66cc66;">&gt;</span> <span style="color: #000000; font-weight: bold;">NULL</span><span style="color: #66cc66;">)</span>,<br />     <span style="color: #66cc66;">)</span>,<br />   <span style="color: #66cc66;">)</span>;<br /><span style="color: #66cc66;">}</span> </div></div> <p>Now that we have a theme implementation that is expecting an order variable, all that remains is to call it as the value for <span class="geshifilter"><code class="php geshifilter-php"><span style="color: #0000ff;">'#markup'</span></code></span> in our output function above.</p> <div class="geshifilter"><div class="drupal6 geshifilter-drupal6" style="font-family:monospace;"><span style="color: #0000ff;">$pane_form</span><span style="color: #66cc66;">[</span><span style="color: #ff0000;">'message'</span><span style="color: #66cc66;">]</span> = <a href="http://www.php.net/array"><span style="color: #000066;">array</span></a><span style="color: #66cc66;">(</span><span style="color: #ff0000;">'#markup'</span> =<span style="color: #66cc66;">&gt;</span> <a href="http://api.drupal.org/api/function/theme/6"><span style="color: #000066;">theme</span></a><span style="color: #66cc66;">(</span><span style="color: #ff0000;">'my_module_completion'</span>, <a href="http://www.php.net/array"><span style="color: #000066;">array</span></a><span style="color: #66cc66;">(</span><span style="color: #ff0000;">'order'</span> =<span style="color: #66cc66;">&gt;</span> <span style="color: #0000ff;">$order</span><span style="color: #66cc66;">)</span><span style="color: #66cc66;">)</span><span style="color: #66cc66;">)</span>;</div></div> <p>From here simply create the <span class="geshifilter"><code class="php geshifilter-php">my_module_completion<span style="color: #339933;">.</span>tpl<span style="color: #339933;">.</span>php</code></span> file and theme away! The template will have our order information exposed as the php variable <span class="geshifilter"><code class="php geshifilter-php"><span style="color: #000088;">$order</span></code></span> allowing us to output whatever order information we would like. This is probably a good time to note, that the <span class="geshifilter"><code class="php geshifilter-php"><span style="color: #000088;">$order</span></code></span> variable very well may not be sufficient (it wasn't for us). It contains mostly keys to fetch other bits of data about the order. In our actual implementation we wound up loading the line item (our implementation only allowed one), and some customer information. This can easily be done from the output function you define in <span class="geshifilter"><code class="php geshifilter-php">hook_commerce_checkout_pane_info_alter</code></span> and passed to the template exactly as we did the order variable in this example.</p> <p><em>I admit that this is bordering on being somewhat hackish.</em> I don't think I would take this approach if the Commerce implementation was more complex. If that were the case a better approach may be to <a href="http://api.drupalcommerce.org/api/Drupal%20Commerce/sites%21all%21modules%21commerce%21modules%21checkout%21commerce_checkout.api.php/function/hook_commerce_checkout_pane_info/DC">create a custom checkout pane</a> with the output you would like, assign it to the appropriate checkout pages, and leave the poor checkout completion pane alone. However, in the interest of time and ease in satisfying a theme requirement for a relatively small site, this method worked like a charm. Further, I don't believe that anything has been obfuscated. With some light commenting it should be apparent to anyone who reviews this code, exactly what is going on. With that in mind, take this method as a handy <em>trick</em> if you need to quickly and drastically rework how the checkout completion pane is displayed.</p></div></div></div> Sun, 12 Aug 2012 18:25:26 +0000 rho 161 at http://theoleschool.com Module Development with NodeJS Integration http://theoleschool.com/blog/module-development-nodejs-integration <div class="field field-name-body field-type-text-with-summary field-label-hidden"><div class="field-items"><div class="field-item even" property="content:encoded"><p>I've been spending some time experimenting with <a href="http://nodejs.org/">NodeJS</a> in relation to the Drupal environment. In many of these Node/Drupal interactions the <a href="http://drupal.org/project/nodejs">NodeJS Integration module</a> has become a key player. There is a very helpful pdf from <a href="http://www.drupalcampnj.org/sessions/drupal-and-nodejs">a talk that beejeebus gave at Drupal camp New Jersey</a> that gives few great visualizations of the underlying principles of how this module works. In essence it "takes care of the plumbing," leaving you, the developer, free to focus on how you would like to integrate. The module is easy to use, and provides a nice set of functions to quickly get rolling. To that point I would like to give a very simple example demonstrating the use of the nodejs integration module.</p> <p>It's perhaps easiest to think of this integration as allowing Drupal to send messages (data) to a client via NodeJS "plumbing." I feel that the simplest and most transparent example is to allow the user to control what that message is and when it is sent. In this demonstration we build a simple form. This form allows a user to enter a message into a text field. Then on submit, it sends that message to all connected sockets and replaces a bit of <span class="geshifilter"><code class="php geshifilter-php"><span style="color: #339933;">&lt;</span>h3<span style="color: #339933;">&gt;</span></code></span> markup within the form. This is very similar to the "broadcast notifications" form included with the NodeJS integration module, but much simpler.</p> <p>To help visualize this I've setup an install to show <a href="http://demo.theoleschool.com/swap">this demo in action</a>. If no one else is viewing the demo page while you are, which very likely will be the case, then I encourage you to open another session (open the page in another browser) and arrange the browser windows so you can see them both at once. This way, when you submit in one session, you can see the text in the header change on the other. I've also checked in <a href="https://github.com/rh0/swapit_demo">the code on github</a> to help fill in the gaps from the snippets I provide here, and hopefully help to make sense of my yammering. So with that, lets get into a bit more detail...</p> <h3 id="considerations">Begin with a few considerations.</h3> <p>When you set out to create your module using NodeJS integration it may help to ask yourself some simple questions.</p> <ul><li>Who receives the message?</li> <li>What is the message?</li> <li>How is the message triggered?</li> <li>What will be done with the message once it is received?</li> </ul><h3 id="who-receives">Who receives the message?</h3> <p>After a user has requested a connection to socket.io, NodeJS sends this authentication token to Drupal. Drupal then sends back a list of channels the user can "listen" on. To manage which channels a user is assigned to we can use <span class="geshifilter"><code class="php geshifilter-php"><span style="color: #000000; font-weight: bold;">function</span> hook_nodejs_user_channels<span style="color: #009900;">(</span><span style="color: #000088;">$auth_user</span><span style="color: #009900;">)</span></code></span>. The <span class="geshifilter"><code class="php geshifilter-php"><span style="color: #000088;">$auth_user</span></code></span> parameter is the user that is being authenticated. Any checks you might need to perform on the user to determine if they should be assigned a channel can be done here.</p><p> </p><p>In our example we're keeping this really simple. We want to send the message to all users so we give everyone who authenticates the 'swapit_demo' channel.</p> <div class="geshifilter"><div class="php geshifilter-php" style="font-family:monospace;"><span style="color: #000000; font-weight: bold;">function</span> swapit_demo_nodejs_user_channels<span style="color: #009900;">(</span><span style="color: #000088;">$auth_user</span><span style="color: #009900;">)</span> <span style="color: #009900;">{</span><br />   <span style="color: #000088;">$channels</span> <span style="color: #339933;">=</span> <a href="http://www.php.net/array"><span style="color: #990000;">array</span></a><span style="color: #009900;">(</span><span style="color: #0000ff;">'swapit_demo'</span><span style="color: #009900;">)</span><span style="color: #339933;">;</span><br />   <span style="color: #b1b100;">return</span> <span style="color: #000088;">$channels</span><span style="color: #339933;">;</span><br /><span style="color: #009900;">}</span></div></div> <h3 id="what-sends">What is the message and how is it triggered?</h3> <p>The message itself is a small object that contains the data it is intended to send, along with some information on how/where to send it. The trigger could really be anything and will likely be pretty obvious based on your use case. In our example module, we're providing the users a form to allow them to write the message that will then be sent on submit. So all of the magic happens in our submit function.</p> <div class="geshifilter"><div class="php geshifilter-php" style="font-family:monospace;"><span style="color: #000000; font-weight: bold;">function</span> swapit_demo_swap_submit<span style="color: #009900;">(</span><span style="color: #000088;">$form</span><span style="color: #339933;">,</span> <span style="color: #339933;">&amp;</span><span style="color: #000088;">$form_state</span><span style="color: #009900;">)</span> <span style="color: #009900;">{</span><br />   <span style="color: #000088;">$user_msg</span> <span style="color: #339933;">=</span> check_plain<span style="color: #009900;">(</span><span style="color: #000088;">$form_state</span><span style="color: #009900;">[</span><span style="color: #0000ff;">'values'</span><span style="color: #009900;">]</span><span style="color: #009900;">[</span><span style="color: #0000ff;">'user_message'</span><span style="color: #009900;">]</span><span style="color: #009900;">)</span><span style="color: #339933;">;</span><br />   <span style="color: #666666; font-style: italic;">//just doing a quick variable set to store the string.</span><br />   variable_set<span style="color: #009900;">(</span><span style="color: #0000ff;">'demo_message'</span><span style="color: #339933;">,</span> <span style="color: #000088;">$user_msg</span><span style="color: #009900;">)</span><span style="color: #339933;">;</span><br />   <span style="color: #666666; font-style: italic;">//broadcasting the string to our listening javascript in waiting</span><br />   <span style="color: #000088;">$message</span> <span style="color: #339933;">=</span> <span style="color: #000000; font-weight: bold;">new</span> stdClass<span style="color: #009900;">(</span><span style="color: #009900;">)</span><span style="color: #339933;">;</span><br />   <span style="color: #000088;">$message</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">channel</span> <span style="color: #339933;">=</span> <span style="color: #0000ff;">'swapit_demo'</span><span style="color: #339933;">;</span><br />   <span style="color: #000088;">$message</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">broadcast</span> <span style="color: #339933;">=</span> <span style="color: #009900; font-weight: bold;">TRUE</span><span style="color: #339933;">;</span><br />   <span style="color: #000088;">$message</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">data</span><span style="color: #009900;">[</span><span style="color: #0000ff;">'body'</span><span style="color: #009900;">]</span> <span style="color: #339933;">=</span> <span style="color: #000088;">$user_msg</span><span style="color: #339933;">;</span><br />   <span style="color: #000088;">$message</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">callback</span> <span style="color: #339933;">=</span> <span style="color: #0000ff;">'swapIt'</span><span style="color: #339933;">;</span><br />   nodejs_send_message<span style="color: #009900;">(</span><span style="color: #000088;">$message</span><span style="color: #009900;">)</span><span style="color: #339933;">;</span><br /><span style="color: #009900;">}</span></div></div> <p>Here we are grabbing the form data from our textfield, and inserting it into a message object. <em>Note how we are setting the channel to the one we set back in <span class="geshifilter"><code class="php geshifilter-php">hook_nodejs_user_channels</code></span>.</em> Passing this object to <span class="geshifilter"><code class="php geshifilter-php">node_send_message</code></span> will get NodeJS to broadcast to all connected sockets having the 'swapit_demo' channel. The <span class="geshifilter"><code class="php geshifilter-php"><span style="color: #000088;">$message</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">data</span></code></span> array contains our user submitted message. The <span class="geshifilter"><code class="php geshifilter-php"><span style="color: #0000ff;">'body'</span></code></span> key is arbitrary.</p><p> </p><h3 id="what-will-be-done">What will be done with the message?</h3> <p>Now that we have a message being broadcast we need to have a bit of javascript on the client side that will receive and do something with it. In our case, we simply want to replace the text in our given HTML element.</p> <div class="geshifilter"><div class="jquery geshifilter-jquery" style="font-family:monospace;">Drupal.<span style="color: #660066;">Nodejs</span>.<span style="color: #660066;">callbacks</span>.<span style="color: #660066;">swapIt</span> <span style="color: #339933;">=</span> <span style="color: #009900;">{</span><br />   <span style="color: #006600; font-style: italic;">//grab the message and inject into the header</span><br />   callback<span style="color: #339933;">:</span> <span style="color: #003366; font-weight: bold;">function</span> <span style="color: #009900;">(</span>message<span style="color: #009900;">)</span> <span style="color: #009900;">{</span><br />     <span style="color: #000066; font-weight: bold;">if</span><span style="color: #009900;">(</span>message.<span style="color: #660066;">channel</span> <span style="color: #339933;">==</span> <span style="color: #3366CC;">'swapit_demo'</span><span style="color: #009900;">)</span> <span style="color: #009900;">{</span><br />       <span style="color: #000066;">$</span><span style="color: #009900;">(</span><span style="color: #3366CC;">'form #nodejs-selector'</span><span style="color: #009900;">)</span>.<a href="http://docs.jquery.com/Attributes/html"><span style="color: #000066;">html</span></a><span style="color: #009900;">(</span>message.<a href="http://docs.jquery.com/Core/data"><span style="color: #000066;">data</span></a>.<span style="color: #660066;">body</span><span style="color: #009900;">)</span><span style="color: #339933;">;</span><br />     <span style="color: #009900;">}</span><br />   <span style="color: #009900;">}</span><br /><span style="color: #009900;">}</span><span style="color: #339933;">;</span></div></div> <p>We first define a callback function to receive the message. Note how the callback name is passed to the message object in the submit function in the previous snippet. Our function now verifies the channel on the message object, and uses the message data to replace the text in the <span class="geshifilter"><code class="php geshifilter-php"><span style="color: #666666; font-style: italic;">#nodejs-selector</span></code></span> element. Notice how <span class="geshifilter"><code class="php geshifilter-php"><span style="color: #000088;">$message</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">data</span><span style="color: #009900;">[</span><span style="color: #0000ff;">'body'</span><span style="color: #009900;">]</span></code></span> has translated to <span class="geshifilter"><code class="jquery geshifilter-jquery">message.<a href="http://docs.jquery.com/Core/data"><span style="color: #000066;">data</span></a>.<span style="color: #660066;">body</span></code></span> in the javascript. All that remains is to tell the nodejs integration module about the file containing our client side javascript.</p> <div class="geshifilter"><div class="php geshifilter-php" style="font-family:monospace;"><span style="color: #000000; font-weight: bold;">function</span> swapit_demo_nodejs_handlers_info<span style="color: #009900;">(</span><span style="color: #009900;">)</span> <span style="color: #009900;">{</span><br />   <span style="color: #b1b100;">return</span> <a href="http://www.php.net/array"><span style="color: #990000;">array</span></a><span style="color: #009900;">(</span><br />     drupal_get_path<span style="color: #009900;">(</span><span style="color: #0000ff;">'module'</span><span style="color: #339933;">,</span> <span style="color: #0000ff;">'swapit_demo'</span><span style="color: #009900;">)</span><span style="color: #339933;">.</span><span style="color: #0000ff;">'/swapit_demo.client.js'</span><span style="color: #339933;">,</span><br />   <span style="color: #009900;">)</span><span style="color: #339933;">;</span><br /><span style="color: #009900;">}</span></div></div> <p>That about sums it up in the simplest way I knew how. I welcome any questions, and would love to hear of others experience with this useful module</p> <h3>Resources:</h3> <ul><li>NodeJS Integration module ( <a href="http://drupal.org/project/nodejs">http://drupal.org/project/nodejs</a> )</li> <li>beejeebus' talk at Drupalcamp NJ ( <a href="http://www.drupalcampnj.org/sessions/drupal-and-nodejs">http://www.drupalcampnj.org/sessions/drupal-and-nodejs</a> )</li> <li>Demonstration of this module ( <a href="http://demo.theoleschool.com/swap">http://demo.theoleschool.com/swap</a> )</li> <li>This code on github ( <a href="https://github.com/rh0/swapit_demo">https://github.com/rh0/swapit_demo</a> )</li> </ul></div></div></div> Sat, 21 Apr 2012 15:49:34 +0000 rho 160 at http://theoleschool.com Using hook_views_alter to Add a GROUP BY Statement http://theoleschool.com/blog/using-hookviewsalter-add-group-statement <div class="field field-name-body field-type-text-with-summary field-label-hidden"><div class="field-items"><div class="field-item even" property="content:encoded"><p><em>I should note this post deals with Views 2.x on Drupal 6.x</em></p> <p>When Views doesn't quite spit out the query we are looking for we can turn to <span class="geshifilter"><code class="php geshifilter-php">hook_views_query_alter<span style="color: #009900;">(</span><span style="color: #339933;">&amp;</span><span style="color: #000088;">$view</span><span style="color: #339933;">,</span> <span style="color: #339933;">&amp;</span><span style="color: #000088;">$query</span><span style="color: #009900;">)</span></code></span> to nudge it in the right direction. This is by no means news, and there are many a blog post detailing various ways to alter the query using this function. However, recently, I ran into an odd issue when attempting to add a <span class="geshifilter"><code class="sql geshifilter-sql"><span style="color: #993333; font-weight: bold;">GROUP</span> <span style="color: #993333; font-weight: bold;">BY</span></code></span> statement.</p> <p>The <a href="http://drupal.org/node/385158">issue</a> can be observed when attempting to use the <a href="http://views.doc.logrus.com/classviews__query.html#089b7eab81ec9cb4b24aa489ff4ac448"><span class="geshifilter"><code class="php geshifilter-php">add_groupby</code></span> function</a> to a field in the query. As an example, say we have a node view with a few fields (node id, node title, and user id). This will give a simple query:</p> <div class="geshifilter"><div class="sql geshifilter-sql" style="font-family:monospace;"><span style="color: #993333; font-weight: bold;">SELECT</span> node<span style="color: #66cc66;">.</span>nid <span style="color: #993333; font-weight: bold;">AS</span> nid<span style="color: #66cc66;">,</span> <br />        node<span style="color: #66cc66;">.</span>title <span style="color: #993333; font-weight: bold;">AS</span> node_title<span style="color: #66cc66;">,</span> <br />        users<span style="color: #66cc66;">.</span>uid <span style="color: #993333; font-weight: bold;">AS</span> users_uid <br /><span style="color: #993333; font-weight: bold;">FROM</span> node node  <br /><span style="color: #993333; font-weight: bold;">INNER</span> <span style="color: #993333; font-weight: bold;">JOIN</span> users users <span style="color: #993333; font-weight: bold;">ON</span> node<span style="color: #66cc66;">.</span>uid <span style="color: #66cc66;">=</span> users<span style="color: #66cc66;">.</span>uid</div></div> <p>Now, say we want to do something silly and get this query to group by user id. A node view does not allow for this, so we turn to <span class="geshifilter"><code class="php geshifilter-php">hook_views_query_alter</code></span>.</p> <div class="geshifilter"><div class="drupal6 geshifilter-drupal6" style="font-family:monospace;"><span style="color: #000000; font-weight: bold;">function</span> my_module_views_query_alter<span style="color: #66cc66;">(</span><span style="color: #66cc66;">&amp;</span><span style="color: #0000ff;">$view</span>, <span style="color: #66cc66;">&amp;</span><span style="color: #0000ff;">$query</span><span style="color: #66cc66;">)</span> <span style="color: #66cc66;">{</span><br />   <span style="color: #b1b100;">switch</span><span style="color: #66cc66;">(</span><span style="color: #0000ff;">$view</span>-<span style="color: #66cc66;">&gt;</span><span style="color: #006600;">name</span><span style="color: #66cc66;">)</span> <span style="color: #66cc66;">{</span><br />     <span style="color: #b1b100;">case</span> <span style="color: #ff0000;">'my_view_name'</span>:<br />       <span style="color: #808080; font-style: italic;">//add the group by  on the user id field </span><br />       <span style="color: #0000ff;">$query</span>-<span style="color: #66cc66;">&gt;</span><span style="color: #006600;">add_groupby</span><span style="color: #66cc66;">(</span><span style="color: #ff0000;">'users_uid'</span><span style="color: #66cc66;">)</span>;<br />       <span style="color: #b1b100;">break</span>;<br />   <span style="color: #66cc66;">}</span><br /><span style="color: #66cc66;">}</span></div></div> <p>We expect to see a nice <span class="geshifilter"><code class="sql geshifilter-sql"><span style="color: #993333; font-weight: bold;">GROUP</span> <span style="color: #993333; font-weight: bold;">BY</span> users_uid</code></span> tacked on to the end of our query, but instead there is a disturbing <span class="geshifilter"><code class="sql geshifilter-sql"><span style="color: #993333; font-weight: bold;">GROUP</span> <span style="color: #993333; font-weight: bold;">BY</span> nid<span style="color: #66cc66;">,</span> node_title<span style="color: #66cc66;">,</span> users_uid</code></span>. What gives? Merlinofchaos <a href="http://drupal.org/node/385158#comment-1296924">clarifies</a> for us:</p> <p><em>"When using GROUP BY items that appear in the SELECT must either use aggregate functions OR appear in the GROUP BY. This is enforced by postgres, therefore Views must enforce this."</em></p> <p>It is good that Views is looking out for the rules of postgres; it's what helps make it the powerful and flexible tool it is. In this application though, I am not worried about postgres and simply want to alter a single Views query on an sql database. I know that I can pass an 'aggregate' flag to the fields in the query object to denote that they use an aggregate function, even if they do not. Worth a try...</p> <div class="geshifilter"><div class="drupal6 geshifilter-drupal6" style="font-family:monospace;"><span style="color: #000000; font-weight: bold;">function</span> dsic_judging_views_query_alter<span style="color: #66cc66;">(</span><span style="color: #66cc66;">&amp;</span><span style="color: #0000ff;">$view</span>, <span style="color: #66cc66;">&amp;</span><span style="color: #0000ff;">$query</span><span style="color: #66cc66;">)</span> <span style="color: #66cc66;">{</span><br />   <span style="color: #b1b100;">switch</span><span style="color: #66cc66;">(</span><span style="color: #0000ff;">$view</span>-<span style="color: #66cc66;">&gt;</span><span style="color: #006600;">name</span><span style="color: #66cc66;">)</span> <span style="color: #66cc66;">{</span><br />     <span style="color: #b1b100;">case</span> <span style="color: #ff0000;">'dsic_judge_incomplete'</span>:<br />       <span style="color: #808080; font-style: italic;">//loop through the fields and add the aggregate indicator</span><br />       <span style="color: #b1b100;">foreach</span><span style="color: #66cc66;">(</span><span style="color: #0000ff;">$query</span>-<span style="color: #66cc66;">&gt;</span><span style="color: #006600;">fields</span> <span style="color: #b1b100;">as</span> <span style="color: #0000ff;">$key</span> =<span style="color: #66cc66;">&gt;</span> <span style="color: #0000ff;">$field</span><span style="color: #66cc66;">)</span> <span style="color: #66cc66;">{</span><br />         <span style="color: #0000ff;">$query</span>-<span style="color: #66cc66;">&gt;</span><span style="color: #006600;">fields</span><span style="color: #66cc66;">[</span><span style="color: #0000ff;">$key</span><span style="color: #66cc66;">]</span><span style="color: #66cc66;">[</span><span style="color: #ff0000;">'aggregate'</span><span style="color: #66cc66;">]</span> = <span style="color: #000000; font-weight: bold;">TRUE</span>;<br />       <span style="color: #66cc66;">}</span><br />       <span style="color: #0000ff;">$query</span>-<span style="color: #66cc66;">&gt;</span><span style="color: #006600;">add_groupby</span><span style="color: #66cc66;">(</span><span style="color: #ff0000;">'users_uid'</span><span style="color: #66cc66;">)</span>;<br />       <span style="color: #b1b100;">break</span>;<br />   <span style="color: #66cc66;">}</span><br /><span style="color: #66cc66;">}</span></div></div> <p>Turns out this works! Jumping back to our view we see the query we are after:</p> <div class="geshifilter"><div class="sql geshifilter-sql" style="font-family:monospace;"><span style="color: #993333; font-weight: bold;">SELECT</span> node<span style="color: #66cc66;">.</span>nid <span style="color: #993333; font-weight: bold;">AS</span> nid<span style="color: #66cc66;">,</span> <br />        node<span style="color: #66cc66;">.</span>title <span style="color: #993333; font-weight: bold;">AS</span> node_title<span style="color: #66cc66;">,</span> <br />        users<span style="color: #66cc66;">.</span>uid <span style="color: #993333; font-weight: bold;">AS</span> users_uid <br /><span style="color: #993333; font-weight: bold;">FROM</span> node node  <br /><span style="color: #993333; font-weight: bold;">INNER</span> <span style="color: #993333; font-weight: bold;">JOIN</span> users users <span style="color: #993333; font-weight: bold;">ON</span> node<span style="color: #66cc66;">.</span>uid <span style="color: #66cc66;">=</span> users<span style="color: #66cc66;">.</span>uid<br /><span style="color: #993333; font-weight: bold;">GROUP</span> <span style="color: #993333; font-weight: bold;">BY</span> users_uid</div></div> <p>If it looks like a hack, and quacks like a hack, then it's probably a... well ya know. However, I don't have big qualms with implementing this. Views behavior is in place to accommodate a database that, in this instance, is not being used. Further, I am already using a hook function to modify a view beyond what that view is capable of spitting out on its own. Lastly, I am able to accomplish this without modifying Views core. All of that adds up to a valid use case and helps me to sleep at night.</p></div></div></div> Mon, 12 Mar 2012 15:09:21 +0000 rho 159 at http://theoleschool.com Viewing Users Who Have Not Submitted a Webform http://theoleschool.com/blog/viewing-users-who-have-not-submitted-webform <div class="field field-name-body field-type-text-with-summary field-label-hidden"><div class="field-items"><div class="field-item even" property="content:encoded"><p>On a recent Drupal 6 site, I received a request to build a view displaying a list of users who had not submitted a specific webform. <a href="http://drupal.org/project/webform&quot;">Webform</a> of course, has some very nice reporting as well as decent views integration for submissions. However, to view users that have yet to make a submission takes some creative views argument handling.</p> <p>I began by building a basic user view. I then set the style and fields to my liking, and filtered on the specific role I was looking for. So now we have views pulling a list of users that we wish to check against. To find out who has not submitted the webform, we first have to take a look at who has.</p> <div class="geshifilter"><div class="drupal6 geshifilter-drupal6" style="font-family:monospace;">webform_get_submissions<span style="color: #66cc66;">(</span><span style="color: #0000ff;">$filters</span> = <a href="http://www.php.net/array"><span style="color: #000066;">array</span></a><span style="color: #66cc66;">(</span><span style="color: #66cc66;">)</span>, <span style="color: #0000ff;">$header</span> = <span style="color: #000000; font-weight: bold;">NULL</span>, <span style="color: #0000ff;">$pager_count</span> = <span style="color: #cc66cc;">0</span><span style="color: #66cc66;">)</span>;</div></div> <p>Webform has the above function in the <span class="geshifilter"><code class="php geshifilter-php">webform<span style="color: #339933;">.</span>submission<span style="color: #339933;">.</span>inc</code></span> file that does just this. You can pass a node id to <span class="geshifilter"><code class="php geshifilter-php"><span style="color: #000088;">$filters</span></code></span> and the function will return a nice array of submission objects, detailing who has submitted the form. We can then add an argument of type <em>"User: UID"</em> to our view, select <em>"Provide default argument"</em> below "Action to take if argument is not present," and select <em>"PHP Code"</em> under "Default argument type." Here we can provide a code snippet to return a list of User ids of users that have submitted the form. </p><div class="geshifilter"><div class="drupal6 geshifilter-drupal6" style="font-family:monospace;"><a href="http://api.drupal.org/api/function/module_load_include/6"><span style="color: #000066;">module_load_include</span></a><span style="color: #66cc66;">(</span><span style="color: #ff0000;">'inc'</span>, <span style="color: #ff0000;">'webform'</span>, <span style="color: #ff0000;">'includes/webform.submissions'</span><span style="color: #66cc66;">)</span>;<br /><span style="color: #0000ff;">$submission_array</span> = webform_get_submissions<span style="color: #66cc66;">(</span><span style="color: #808080; font-style: italic;">/*node id of webform we are checking for*/</span><span style="color: #66cc66;">)</span>;<br /><span style="color: #0000ff;">$uids</span> = <a href="http://www.php.net/array"><span style="color: #000066;">array</span></a><span style="color: #66cc66;">(</span><span style="color: #66cc66;">)</span>;<br /><span style="color: #b1b100;">foreach</span><span style="color: #66cc66;">(</span><span style="color: #0000ff;">$submission_array</span> <span style="color: #b1b100;">as</span> <span style="color: #0000ff;">$submission</span><span style="color: #66cc66;">)</span> <span style="color: #66cc66;">{</span><br />   <span style="color: #0000ff;">$uids</span><span style="color: #66cc66;">[</span><span style="color: #66cc66;">]</span> = <span style="color: #0000ff;">$submission</span>-<span style="color: #66cc66;">&gt;</span><span style="color: #006600;">uid</span>;<br /><span style="color: #66cc66;">}</span><br /><span style="color: #b1b100;">return</span> <a href="http://www.php.net/implode"><span style="color: #000066;">implode</span></a><span style="color: #66cc66;">(</span><span style="color: #ff0000;">','</span>, <span style="color: #0000ff;">$uids</span><span style="color: #66cc66;">)</span>;</div></div> <p>This will gives us a comma separated string of user ids. The default "Validator options" are fine but <strong>be sure</strong> to check <em>"Allow multiple terms per argument"</em> and <em>"Exclude the argument."</em> After updating, your view now filters down to users that have not submitted the webform! To double check that this is happening, you can look at the query below the preview. Near the end of the query in the <span class="geshifilter"><code class="sql geshifilter-sql"><span style="color: #993333; font-weight: bold;">WHERE</span></code></span> clause, you should see something like <span class="geshifilter"><code class="sql geshifilter-sql">users<span style="color: #66cc66;">.</span>uid <span style="color: #993333; font-weight: bold;">NOT</span> <span style="color: #993333; font-weight: bold;">IN</span> <span style="color: #66cc66;">(</span><span style="color: #cc66cc;">110</span><span style="color: #66cc66;">,</span> <span style="color: #cc66cc;">13028</span><span style="color: #66cc66;">,</span> <span style="color: #cc66cc;">210</span><span style="color: #66cc66;">,</span> <span style="color: #cc66cc;">107</span><span style="color: #66cc66;">,</span> <span style="color: #cc66cc;">35329</span><span style="color: #66cc66;">,</span> <span style="color: #cc66cc;">35330</span><span style="color: #66cc66;">,</span> <span style="color: #cc66cc;">35331</span><span style="color: #66cc66;">)</span></code></span>. The list of numbers are the user ids that are being filtered. If no one has submitted the form, then this of course will be empty.</p> <p>Now I admit, this is kinda gross. I shudder any time I include php in configuration. It is, however, a very straight forward way to get the results requested while staying in views, therefore leaving the output in the hands of a site builder/administrator. There may be some clever ways to achieve the same thing using something like <a href="http://drupal.org/project/webform_report">Webform Report</a>, or even a different views configuration, but nothing jumped out at me. However, if you have solved this in a different way I would love to hear about it.</p></div></div></div> Sun, 04 Mar 2012 18:25:05 +0000 rho 158 at http://theoleschool.com Using the Data Module's "Relate to Node" Functionality http://theoleschool.com/blog/using-data-modules-relate-node-functionality <div class="field field-name-body field-type-text-with-summary field-label-hidden"><div class="field-items"><div class="field-item even" property="content:encoded"><p>While I'm <a href="/blog/adopting-table-schema-using-data-module">on the topic</a> of the <a href="http://drupal.org/project/data">Data module</a>, I'd like to share my thoughts on some of the confusion surrounding use of the Data Node module included with the package. (see issues <a href="http://drupal.org/node/976292">976292</a> and <a href="http://drupal.org/node/898562">898562</a>)</p> <p>It's my opinion that the bulk of the functionality in the Data Node module has been intentionally "left undone." Instead, the module is giving us the tools to allow us to implement node relationships as needed, ourselves (think API). My thinking is that this was done to allow the maximum amount of flexibility in relating other data to nodes by minimizing assumptions about what the relationship should be. <em>I have in no way validated this with any of the module maintainers, nor is it denoted in any documentation that I am aware of.</em> What follows is my "average" use case for this module, and some thoughts on more advanced implementations.</p> <img src="/sites/default/files/u3/relate-to-node-form.png" style="float:right; margin: 0 0 0 10px; width:250px;" /><p>When enabled, this functionality is apparent in Drupal's admin UI at <span class="geshifilter"><code class="php geshifilter-php">admin<span style="color: #339933;">/</span>build<span style="color: #339933;">/</span>data<span style="color: #339933;">/</span>edit<span style="color: #339933;">/%</span>table_name<span style="color: #339933;">/</span>node</code></span> by clicking on the edit link next to a table at <span class="geshifilter"><code class="php geshifilter-php">admin<span style="color: #339933;">/</span>build<span style="color: #339933;">/</span>data<span style="color: #339933;">/</span>overview</code></span> then clicking the "Relate to Nodes" tab. This page presents a form asking you which content type you would like the table related to, and the column name of the id key in the table you are referencing. Simple right? Well sorta... When you submit this form and happily skip over to views to reference the node table, it quickly becomes apparent that nothing has happened. Checking the <span class="geshifilter"><code class="php geshifilter-php">data_table_node</code></span> table in the database will reveal it to be empty. So what's up here? Is this just an unfinished piece?</p> <p>After taking a look in the data_node.module things start to make a bit more sense. At first glance we see some tantalizing functions, especially:</p> <div class="geshifilter"><div class="drupal6 geshifilter-drupal6" style="font-family:monospace;"><span style="color: #808080; font-style: italic;">/**<br />  * Add a relationship between a data table and a node.<br />  */</span><br /><span style="color: #000000; font-weight: bold;">function</span> data_node_add<span style="color: #66cc66;">(</span><span style="color: #0000ff;">$table</span>, <span style="color: #0000ff;">$id</span>, <span style="color: #0000ff;">$nid</span><span style="color: #66cc66;">)</span> <span style="color: #66cc66;">{</span><br />   <span style="color: #0000ff;">$save</span> = <a href="http://www.php.net/array"><span style="color: #000066;">array</span></a><span style="color: #66cc66;">(</span><br />     <span style="color: #ff0000;">'data_table_name'</span> =<span style="color: #66cc66;">&gt;</span> <span style="color: #0000ff;">$table</span>-<span style="color: #66cc66;">&gt;</span><span style="color: #006600;">get</span><span style="color: #66cc66;">(</span><span style="color: #ff0000;">'name'</span><span style="color: #66cc66;">)</span>,<br />     <span style="color: #ff0000;">'id'</span> =<span style="color: #66cc66;">&gt;</span> <span style="color: #0000ff;">$id</span>,<br />     <span style="color: #ff0000;">'nid'</span> =<span style="color: #66cc66;">&gt;</span> <span style="color: #0000ff;">$nid</span>,<br />   <span style="color: #66cc66;">)</span>;<br />   <span style="color: #b1b100;">return</span> <a href="http://api.drupal.org/api/function/drupal_write_record/6"><span style="color: #000066;">drupal_write_record</span></a><span style="color: #66cc66;">(</span><span style="color: #ff0000;">'data_table_node'</span>, <span style="color: #0000ff;">$save</span><span style="color: #66cc66;">)</span>;<br /><span style="color: #66cc66;">}</span><br /><span style="color: #808080; font-style: italic;">/**<br />  * Remove a relationship between a data table and a node.<br />  */</span><br /><span style="color: #000000; font-weight: bold;">function</span> data_node_remove<span style="color: #66cc66;">(</span><span style="color: #0000ff;">$table</span>, <span style="color: #0000ff;">$id</span>, <span style="color: #0000ff;">$nid</span><span style="color: #66cc66;">)</span> <span style="color: #66cc66;">{</span><br />   <a href="http://api.drupal.org/api/function/db_query/6"><span style="color: #000066;">db_query</span></a><span style="color: #66cc66;">(</span><span style="color: #ff0000;">"DELETE FROM {data_table_node} WHERE data_table_name = '%s' AND id = %d AND nid = %d"</span>, <span style="color: #0000ff;">$table</span>-<span style="color: #66cc66;">&gt;</span><span style="color: #006600;">get</span><span style="color: #66cc66;">(</span><span style="color: #ff0000;">'name'</span><span style="color: #66cc66;">)</span>, <span style="color: #0000ff;">$id</span>, <span style="color: #0000ff;">$nid</span><span style="color: #66cc66;">)</span>;<br /><span style="color: #66cc66;">}</span></div></div> <p>Typically I'll stop right here. I really only use the Data module for custom reporting, and when doing so I handle the CRUD functions for the new data type myself. My data type's row reference to nodes are nearly always a 1 to 1 mapping. To further simplify things for myself, the <span class="geshifilter"><code class="php geshifilter-php"><span style="color: #000088;">$id</span></code></span> I use in my custom data type is often the node id of it's intended reference. This makes my calls to these functions look something like:</p> <div class="geshifilter"><div class="drupal6 geshifilter-drupal6" style="font-family:monospace;">  <span style="color: #808080; font-style: italic;">//insert my_table with a nid key</span><br />   ...<br />   <span style="color: #808080; font-style: italic;">//inform Data Node of the reference</span><br />   <span style="color: #0000ff;">$my_table_object</span> = data_get_table<span style="color: #66cc66;">(</span><span style="color: #ff0000;">'my_table'</span><span style="color: #66cc66;">)</span>;<br />   data_node_add<span style="color: #66cc66;">(</span><span style="color: #0000ff;">$my_table_object</span>, <span style="color: #0000ff;">$nid</span>, <span style="color: #0000ff;">$nid</span><span style="color: #66cc66;">)</span>;</div></div> <p>This will populate the <span class="geshifilter"><code class="php geshifilter-php">data_table_node</code></span> table so that when we use the relationship in out data type view it will return results. Of course deletion will be handled similarly.</p> <p>This is admittedly a somewhat basal interaction with this module, though it is often enough to satisfy my needs. However Data Node allows for much more complexity and has a certain level of user interaction built in just below the surface.</p> <p>It's not until we start thinking about the data structures involved that the scope of the allowed complexity becomes apparent. It is easy to assume that referencing a data type to a node is always going to be 1 to 1 (much like the typical use of CCK's Node Reference.) However, this may not always be the case. It is not required that the data table's id be the nid of the node it is referencing. It only has to be unique within the table. Because of this, a large field of possibilities emerge. You may have data type rows referencing many nodes each (1 to many), many data type rows referencing a single node (many to 1), or, of course, any number of data type rows referencing any number of nodes (many to many). The <span class="geshifilter"><code class="php geshifilter-php">data_table_node</code></span> table along with the provided CRUD functions, like the ones mentioned above, will allow for this. I think that this flexibility is a big reason that the module has been left so open.</p> <p>Lastly, a closer look at the Data Node module will reveal some rather fancy display functions for the underlying relationship CRUD.</p> <div class="geshifilter"><div class="drupal6 geshifilter-drupal6" style="font-family:monospace;"><span style="color: #808080; font-style: italic;">/**<br />  * Render an add link for a given item..<br />  */</span><br /><span style="color: #000000; font-weight: bold;">function</span> data_node_render_add_link<span style="color: #66cc66;">(</span><span style="color: #0000ff;">$table</span>, <span style="color: #0000ff;">$id</span>, <span style="color: #0000ff;">$nid</span><span style="color: #66cc66;">)</span> <span style="color: #66cc66;">{</span><br />   <a href="http://api.drupal.org/api/function/drupal_add_css/6"><span style="color: #000066;">drupal_add_css</span></a><span style="color: #66cc66;">(</span><a href="http://api.drupal.org/api/function/drupal_get_path/6"><span style="color: #000066;">drupal_get_path</span></a><span style="color: #66cc66;">(</span><span style="color: #ff0000;">'module'</span>, <span style="color: #ff0000;">'data_node'</span><span style="color: #66cc66;">)</span> . <span style="color: #ff0000;">'/data_node.css'</span><span style="color: #66cc66;">)</span>;<br />   <a href="http://api.drupal.org/api/function/drupal_add_js/6"><span style="color: #000066;">drupal_add_js</span></a><span style="color: #66cc66;">(</span><a href="http://api.drupal.org/api/function/drupal_get_path/6"><span style="color: #000066;">drupal_get_path</span></a><span style="color: #66cc66;">(</span><span style="color: #ff0000;">'module'</span>, <span style="color: #ff0000;">'data_node'</span><span style="color: #66cc66;">)</span> . <span style="color: #ff0000;">'/data_node.js'</span><span style="color: #66cc66;">)</span>;<br /><br />   <span style="color: #0000ff;">$title</span> = _data_node_get_title<span style="color: #66cc66;">(</span><span style="color: #0000ff;">$nid</span><span style="color: #66cc66;">)</span>;<br />   <span style="color: #0000ff;">$table_name</span> = <span style="color: #0000ff;">$table</span>-<span style="color: #66cc66;">&gt;</span><span style="color: #006600;">get</span><span style="color: #66cc66;">(</span><span style="color: #ff0000;">'name'</span><span style="color: #66cc66;">)</span>;<br />   <span style="color: #0000ff;">$class</span> = <span style="color: #ff0000;">"data_node_link-{$table_name}-{$id}-{$nid}"</span>;<br />   <span style="color: #b1b100;">return</span> <a href="http://api.drupal.org/api/function/l/6"><span style="color: #000066;">l</span></a><span style="color: #66cc66;">(</span><a href="http://api.drupal.org/api/function/t/6"><span style="color: #000066;">t</span></a><span style="color: #66cc66;">(</span><span style="color: #ff0000;">'Add to !title'</span>, <a href="http://www.php.net/array"><span style="color: #000066;">array</span></a><span style="color: #66cc66;">(</span><span style="color: #ff0000;">'!title'</span> =<span style="color: #66cc66;">&gt;</span> <span style="color: #0000ff;">$title</span><span style="color: #66cc66;">)</span><span style="color: #66cc66;">)</span>, data_node_add_path<span style="color: #66cc66;">(</span><span style="color: #0000ff;">$table</span>, <span style="color: #0000ff;">$id</span>, <span style="color: #0000ff;">$nid</span><span style="color: #66cc66;">)</span>, <a href="http://www.php.net/array"><span style="color: #000066;">array</span></a><span style="color: #66cc66;">(</span><span style="color: #ff0000;">'attributes'</span> =<span style="color: #66cc66;">&gt;</span> <a href="http://www.php.net/array"><span style="color: #000066;">array</span></a><span style="color: #66cc66;">(</span><span style="color: #ff0000;">'class'</span> =<span style="color: #66cc66;">&gt;</span> <span style="color: #ff0000;">"data-node-add $class"</span><span style="color: #66cc66;">)</span>, <span style="color: #ff0000;">'query'</span> =<span style="color: #66cc66;">&gt;</span> <a href="http://api.drupal.org/api/function/drupal_get_destination/6"><span style="color: #000066;">drupal_get_destination</span></a><span style="color: #66cc66;">(</span><span style="color: #66cc66;">)</span><span style="color: #66cc66;">)</span><span style="color: #66cc66;">)</span>;<br /><span style="color: #66cc66;">}</span><br /><br /><span style="color: #808080; font-style: italic;">/**<br />  * Render a remove link for a given item.<br />  */</span><br /><span style="color: #000000; font-weight: bold;">function</span> data_node_render_remove_link<span style="color: #66cc66;">(</span><span style="color: #0000ff;">$table</span>, <span style="color: #0000ff;">$id</span>, <span style="color: #0000ff;">$nid</span><span style="color: #66cc66;">)</span> <span style="color: #66cc66;">{</span><br />   <a href="http://api.drupal.org/api/function/drupal_add_css/6"><span style="color: #000066;">drupal_add_css</span></a><span style="color: #66cc66;">(</span><a href="http://api.drupal.org/api/function/drupal_get_path/6"><span style="color: #000066;">drupal_get_path</span></a><span style="color: #66cc66;">(</span><span style="color: #ff0000;">'module'</span>, <span style="color: #ff0000;">'data_node'</span><span style="color: #66cc66;">)</span> . <span style="color: #ff0000;">'/data_node.css'</span><span style="color: #66cc66;">)</span>;<br />   <a href="http://api.drupal.org/api/function/drupal_add_js/6"><span style="color: #000066;">drupal_add_js</span></a><span style="color: #66cc66;">(</span><a href="http://api.drupal.org/api/function/drupal_get_path/6"><span style="color: #000066;">drupal_get_path</span></a><span style="color: #66cc66;">(</span><span style="color: #ff0000;">'module'</span>, <span style="color: #ff0000;">'data_node'</span><span style="color: #66cc66;">)</span> . <span style="color: #ff0000;">'/data_node.js'</span><span style="color: #66cc66;">)</span>;<br /><br />   <span style="color: #0000ff;">$title</span> = _data_node_get_title<span style="color: #66cc66;">(</span><span style="color: #0000ff;">$nid</span><span style="color: #66cc66;">)</span>;<br />   <span style="color: #0000ff;">$table_name</span> = <span style="color: #0000ff;">$table</span>-<span style="color: #66cc66;">&gt;</span><span style="color: #006600;">get</span><span style="color: #66cc66;">(</span><span style="color: #ff0000;">'name'</span><span style="color: #66cc66;">)</span>;<br />   <span style="color: #0000ff;">$class</span> = <span style="color: #ff0000;">"data_node_link-{$table_name}-{$id}-{$nid}"</span>;<br />   <span style="color: #b1b100;">return</span> <a href="http://api.drupal.org/api/function/l/6"><span style="color: #000066;">l</span></a><span style="color: #66cc66;">(</span><a href="http://api.drupal.org/api/function/t/6"><span style="color: #000066;">t</span></a><span style="color: #66cc66;">(</span><span style="color: #ff0000;">'Remove from !title'</span>, <a href="http://www.php.net/array"><span style="color: #000066;">array</span></a><span style="color: #66cc66;">(</span><span style="color: #ff0000;">'!title'</span> =<span style="color: #66cc66;">&gt;</span> <span style="color: #0000ff;">$title</span><span style="color: #66cc66;">)</span><span style="color: #66cc66;">)</span>, data_node_remove_path<span style="color: #66cc66;">(</span><span style="color: #0000ff;">$table</span>, <span style="color: #0000ff;">$id</span>, <span style="color: #0000ff;">$nid</span><span style="color: #66cc66;">)</span>, <a href="http://www.php.net/array"><span style="color: #000066;">array</span></a><span style="color: #66cc66;">(</span><span style="color: #ff0000;">'attributes'</span> =<span style="color: #66cc66;">&gt;</span> <a href="http://www.php.net/array"><span style="color: #000066;">array</span></a><span style="color: #66cc66;">(</span><span style="color: #ff0000;">'class'</span> =<span style="color: #66cc66;">&gt;</span> <span style="color: #ff0000;">"data-node-remove $class"</span><span style="color: #66cc66;">)</span>, <span style="color: #ff0000;">'query'</span> =<span style="color: #66cc66;">&gt;</span> <a href="http://api.drupal.org/api/function/drupal_get_destination/6"><span style="color: #000066;">drupal_get_destination</span></a><span style="color: #66cc66;">(</span><span style="color: #66cc66;">)</span><span style="color: #66cc66;">)</span><span style="color: #66cc66;">)</span>;<br /><span style="color: #66cc66;">}</span></div></div> <p>These are the two pertinent functions. They both return a link that will hit a page callback to do just what you would expect. Add or remove a node relationship. This allows you "expose" the functionality to the user. The link could be placed on a view or node page to allow users to add or remove the relation to a specific data type themselves, "on the fly". This, in combination with the flexibility of the relationship itself, lends to a huge variety of possible use cases with relatively few function calls.</p> <p>So wrapping this up. Data Node module is best thought of as an API. It doesn't do so much right out of the box, but with minimal implementation it becomes a fairly powerful tool.</p></div></div></div> Sun, 04 Mar 2012 03:44:18 +0000 rho 157 at http://theoleschool.com Adopting a Table with Schema using Data Module http://theoleschool.com/blog/adopting-table-schema-using-data-module <div class="field field-name-body field-type-text-with-summary field-label-hidden"><div class="field-items"><div class="field-item even" property="content:encoded"><p>When working with medium to larger sized web applications, inevitably the requirement comes up for some advanced data reporting. The spectrum of requests here can obviously be pretty broad and is often beyond what you can accomplish with the default tables provided by Drupal and the installed modules. This tends to lead us to creating custom tables to track and manage the requested reports. Along with writing all of the necessary <a href="http://en.wikipedia.org/wiki/Create,_read,_update_and_delete">CRUD</a> functions to make sure our new table is managing data appropriately, we also have to provide some sort of display or an export. Views can handle this latter requirement quite nicely. So it is here that we can turn to modules like <a href="http://drupal.org/project/data">Data</a> or the now depricated <a href="http://drupal.org/project/tw">Table Wizard</a> to bring our new table under views "control." This gives us the added bonus of taking the display configuration out of the hands of the developer and placing it into the site builder/administrator's domain.</p> <p>In a recent project just such a reporting request came up. The report was fairly complex and was to be visible to a larger subset of users. I wound up managing this report with a custom table and I turned to Data module to allow views to handle the output. After installing my custom module which created the custom table, I navigated to <span class="geshifilter"><code class="php geshifilter-php">admin<span style="color: #339933;">/</span>build<span style="color: #339933;">/</span>data<span style="color: #339933;">/</span>adopt</code></span>. Expecting to see my table in the list of "adoptable" tables, I was a bit confused when it was nowhere to be found. After <a href="http://drupal.org/node/888946#comment-3360326">a bit of digging</a> I realized that tables declared using <a href="http://drupal.org/node/146843">Schema API</a> were considered "owned" by the module and not included in the list of adoptable tables. This makes some sense as you don't want just any table adopted by Data. Just imagine when an admin who's not quite sure what he or she is doing, decides to adopt and then drop the node table. I'd rather not...</p> <p>So this leaves us with the question, <em>"How do I get Data module to adopt my table?"</em> From what I can tell, the creators of the data module expect us to use the Data API to handle table creation and CRUD. Personally I don't like the idea of this. I already understand and like working with Drupal's core Schema API, and don't want to adopt another layer of abstraction. I only want to utilize Data module to throw my table up to views. So after looking through how adoption is handled in the module, I found that really there is nothing preventing Data from adopting these schema declared tables. They are simply being made unavailable to the form at <span class="geshifilter"><code class="php geshifilter-php">admin<span style="color: #339933;">/</span>build<span style="color: #339933;">/</span>data<span style="color: #339933;">/</span>adopt</code></span>. This lead me to attempt to adopt my table right after it is generated in my module's .install file. This wound up working quite nicely and is implemented as follows:</p> <div class="geshifilter"><div class="drupal6 geshifilter-drupal6" style="font-family:monospace;"><span style="color: #000000; font-weight: bold;">function</span> my_module_install<span style="color: #66cc66;">(</span><span style="color: #66cc66;">)</span> <span style="color: #66cc66;">{</span><br />   <span style="color: #808080; font-style: italic;">//install schema as normal</span><br />   <a href="http://api.drupal.org/api/function/drupal_install_schema/6"><span style="color: #000066;">drupal_install_schema</span></a><span style="color: #66cc66;">(</span><span style="color: #ff0000;">'my_table'</span><span style="color: #66cc66;">)</span>;<br />   <br />   <span style="color: #808080; font-style: italic;">//make sure we have the data module</span><br />   <span style="color: #b1b100;">include_once</span><span style="color: #66cc66;">(</span><a href="http://api.drupal.org/api/function/drupal_get_path/6"><span style="color: #000066;">drupal_get_path</span></a><span style="color: #66cc66;">(</span><span style="color: #ff0000;">'module'</span>, <span style="color: #ff0000;">'data'</span><span style="color: #66cc66;">)</span> .<span style="color: #ff0000;">'/data.module'</span><span style="color: #66cc66;">)</span>;<br />   data_include<span style="color: #66cc66;">(</span><span style="color: #ff0000;">'DataTable'</span><span style="color: #66cc66;">)</span>;<br />   <span style="color: #808080; font-style: italic;">//instanciate the table we just created with the DataTable class</span><br />   <span style="color: #0000ff;">$my_table_object</span> = DataTable::<span style="color: #006600;">instance</span><span style="color: #66cc66;">(</span><span style="color: #ff0000;">'my_table'</span><span style="color: #66cc66;">)</span>;<br />   <span style="color: #808080; font-style: italic;">//tell Data module to adopt the table</span><br />   <span style="color: #0000ff;">$my_table_object</span>-<span style="color: #66cc66;">&gt;</span><span style="color: #006600;">adopt</span><span style="color: #66cc66;">(</span><span style="color: #66cc66;">)</span>;<br />   DataTable::<span style="color: #006600;">clearCaches</span><span style="color: #66cc66;">(</span><span style="color: #66cc66;">)</span>;<br /><span style="color: #66cc66;">}</span></div></div> <p>After installing the module you should be able to see your custom table at <span class="geshifilter"><code class="php geshifilter-php">admin<span style="color: #339933;">/</span>build<span style="color: #339933;">/</span>data</code></span>. Data module is now obviously a dependency so be sure that it is installed and enabled before installing your custom module.</p> <p>I realize that I could write my own views handlers for the custom table data I am providing, but I have found that to be a fairly time consuming (and often esoteric) task. When all we are after is a single, somewhat unique report, I feel that integration with the Data module and it's corresponding integration with views is simple, quick, and more than adequate.</p></div></div></div> Sat, 03 Mar 2012 20:19:36 +0000 rho 156 at http://theoleschool.com Quick Stock Quote on Page Refresh http://theoleschool.com/blog/quick-stock-quote-page-refresh-0 <div class="field field-name-body field-type-text-with-summary field-label-hidden"><div class="field-items"><div class="field-item even" property="content:encoded"><p>I thought I would throw a post up, demonstrating a "quick and easy" use case of the <a href="http://drupal.org/project/stockapi">Stock API module</a>, and simultaneously, give this site's <a href="http://qbnz.com/highlighter/">GeSHi filter</a> a quick test run.</p> <p>A few days ago I encountered a site requirement asking for a simple informative stock quote block on a Drupal 6.x installation. The format required was something like: <br /><em>NFLX 208.75 -1.30 Sep 13 7:30 EST</em> <br /> My initial thought was to enable the <a href="http://drupal.org/project/stock">Stock module</a> and simply configure and theme the built in block. The stock module will allow the management of multiple stocks on a per-user basis, and provides a block that displays this information in a table. There is no doubt that stock module is useful but in this instance it is overkill. All we are after is a simple quote from a single unchanging stock symbol displayed in a single line of text.</p> <p>So let's take a short step back to the stock API module. There is really one "go to" function when grabbing a quote from the stock api and that is <span class="geshifilter"><code class="php geshifilter-php">stockapi_load<span style="color: #009900;">(</span><span style="color: #000088;">$symbol</span><span style="color: #009900;">)</span></code></span>. This function is defined like so:</p> <div class="geshifilter"><div class="drupal6 geshifilter-drupal6" style="font-family:monospace;"><span style="color: #000000; font-weight: bold;">function</span> stockapi_load<span style="color: #66cc66;">(</span><span style="color: #0000ff;">$symbol</span><span style="color: #66cc66;">)</span> <span style="color: #66cc66;">{</span><br />   <span style="color: #b1b100;">if</span> <span style="color: #66cc66;">(</span><span style="color: #66cc66;">!</span><a href="http://www.php.net/empty"><span style="color: #000066;">empty</span></a><span style="color: #66cc66;">(</span><span style="color: #0000ff;">$symbol</span><span style="color: #66cc66;">)</span><span style="color: #66cc66;">)</span> <span style="color: #66cc66;">{</span><br />     <span style="color: #0000ff;">$stock</span> = <a href="http://api.drupal.org/api/function/db_fetch_array/6"><span style="color: #000066;">db_fetch_array</span></a><span style="color: #66cc66;">(</span><a href="http://api.drupal.org/api/function/db_query/6"><span style="color: #000066;">db_query</span></a><span style="color: #66cc66;">(</span><span style="color: #ff0000;">"SELECT * FROM {stockapi} WHERE symbol = '%s'"</span>, <span style="color: #0000ff;">$symbol</span><span style="color: #66cc66;">)</span><span style="color: #66cc66;">)</span>;<br />     <span style="color: #b1b100;">if</span> <span style="color: #66cc66;">(</span><span style="color: #66cc66;">!</span><span style="color: #0000ff;">$stock</span><span style="color: #66cc66;">)</span> <span style="color: #66cc66;">{</span><br />       <span style="color: #0000ff;">$stock</span> = stockapi_fetch<span style="color: #66cc66;">(</span><span style="color: #0000ff;">$symbol</span><span style="color: #66cc66;">)</span>;<br />       <span style="color: #b1b100;">if</span> <span style="color: #66cc66;">(</span><span style="color: #0000ff;">$stock</span><span style="color: #66cc66;">)</span> <span style="color: #66cc66;">{</span><br />         <span style="color: #b1b100;">if</span> <span style="color: #66cc66;">(</span><span style="color: #0000ff;">$stock</span><span style="color: #66cc66;">[</span><span style="color: #cc66cc;">8</span><span style="color: #66cc66;">]</span> <span style="color: #66cc66;">!</span>= <span style="color: #ff0000;">'N/A'</span><span style="color: #66cc66;">)</span> <span style="color: #66cc66;">{</span><br />           <span style="color: #808080; font-style: italic;">// Date is 'N/A' means an invalid stock symbol</span><br />           stockapi_save<span style="color: #66cc66;">(</span><span style="color: #0000ff;">$stock</span><span style="color: #66cc66;">)</span>;<br /><br />           <span style="color: #808080; font-style: italic;">// Load again so it can show for the first time</span><br />           <span style="color: #0000ff;">$stock</span> = stockapi_load<span style="color: #66cc66;">(</span><span style="color: #0000ff;">$symbol</span><span style="color: #66cc66;">)</span>;<br />         <span style="color: #66cc66;">}</span><br />       <span style="color: #66cc66;">}</span><br />     <span style="color: #66cc66;">}</span><br />   <span style="color: #66cc66;">}</span><br />   <span style="color: #b1b100;">return</span> <span style="color: #0000ff;">$stock</span>;<br /><span style="color: #66cc66;">}</span></div></div> <p>As you can see the stock API module is wanting to store stock information in the database to be updated on cron runs. It does this as a type of database caching. This way if a site is pulling information on many stocks, potentially every page load, compounded by many users the API can retrieve the information with a simple query to the local database. Not only is this method much faster than hitting the third party service (yahoo) every time, it is able to serve saved information (especially handy if that service happens to be down or a connection cannot be established). This feature along with returning a nice keyed array of stock information is reason enough to love this function. However, I think we could go a bit simpler still. We only need information on one stock site wide, and in this instance that information should be as current as possible. Furthermore, I would like to avoid setting up a cron job to run every half hour just to keep stock information current. Rather, we look further into the Stock API and make a more basal call to <span class="geshifilter"><code class="php geshifilter-php">stockapi_fetch<span style="color: #009900;">(</span><span style="color: #000088;">$symbol</span><span style="color: #009900;">)</span><span style="color: #339933;">;</span></code></span>. This function is a direct call to the 3rd party service, in this case yahoo:</p> <div class="geshifilter"><div class="drupal6 geshifilter-drupal6" style="font-family:monospace;"><span style="color: #808080; font-style: italic;">/**<br />  * Fetch the stock info for a given ticker symbol from Yahoo!<br />  */</span><br /><span style="color: #000000; font-weight: bold;">function</span> stockapi_fetch<span style="color: #66cc66;">(</span><span style="color: #0000ff;">$symbol</span><span style="color: #66cc66;">)</span> <span style="color: #66cc66;">{</span><br />   <span style="color: #b1b100;">if</span> <span style="color: #66cc66;">(</span><a href="http://www.php.net/empty"><span style="color: #000066;">empty</span></a><span style="color: #66cc66;">(</span><span style="color: #0000ff;">$symbol</span><span style="color: #66cc66;">)</span><span style="color: #66cc66;">)</span> <span style="color: #66cc66;">{</span><br />     <span style="color: #b1b100;">return</span> <span style="color: #000000; font-weight: bold;">FALSE</span>;<br />   <span style="color: #66cc66;">}</span><br /><br />   <span style="color: #0000ff;">$fields</span> = <span style="color: #ff0000;">'snl1c1ohgvd1t1'</span>;<br />   <span style="color: #808080; font-style: italic;">/*<br />     "s"  =&gt; "Symbol",<br />     "n"  =&gt; "Name",<br />     "l1" =&gt; "Last",<br />     "c1" =&gt; "Change",<br />     "o"  =&gt; "Opening",<br />     "h"  =&gt; "High",<br />     "g"  =&gt; "Low",<br />     "v"  =&gt; "Volume",<br />     "d1" =&gt; "Date",<br />     "t1" =&gt; "Time"<br />    */</span><br /><br />   <span style="color: #0000ff;">$host</span> = <span style="color: #ff0000;">'http://download.finance.yahoo.com'</span>;<br />   <span style="color: #0000ff;">$url</span> = <span style="color: #0000ff;">$host</span> .<span style="color: #ff0000;">'/d/quotes.csv?s='</span>. <a href="http://www.php.net/urlencode"><span style="color: #000066;">urlencode</span></a><span style="color: #66cc66;">(</span><span style="color: #0000ff;">$symbol</span><span style="color: #66cc66;">)</span> .<span style="color: #ff0000;">'&amp;f='</span>.<span style="color: #0000ff;">$fields</span>.<span style="color: #ff0000;">'&amp;e=.csv'</span>;<br /><br />   <span style="color: #0000ff;">$result</span> = <a href="http://api.drupal.org/api/function/drupal_http_request/6"><span style="color: #000066;">drupal_http_request</span></a><span style="color: #66cc66;">(</span><span style="color: #0000ff;">$url</span><span style="color: #66cc66;">)</span>;<br />   ...</div></div> <p>This ensures fresh data, without a cron run to update the database. This function is not quite as nice as the <span class="geshifilter"><code class="php geshifilter-php">stockapi_load<span style="color: #009900;">(</span><span style="color: #009900;">)</span></code></span> function in that the array it returns is not keyed. The commented section above (from the module) comes to the rescue. It is in the same sequence as the array returned so it can be used as a reference. So to return something like our requirement example above:<br /><em>NFLX 208.75 -1.30 Sep 13 7:30 EST</em><br /> We would write something like:</p> <div class="geshifilter"><div class="drupal6 geshifilter-drupal6" style="font-family:monospace;"><span style="color: #0000ff;">$output</span> = <span style="color: #ff0000;">''</span>;<br /><span style="color: #0000ff;">$my_stock</span> = stockapi_fetch<span style="color: #66cc66;">(</span><span style="color: #ff0000;">'NFLX'</span><span style="color: #66cc66;">)</span>;<br /><span style="color: #b1b100;">if</span><span style="color: #66cc66;">(</span><span style="color: #0000ff;">$my_stock</span><span style="color: #66cc66;">)</span> <span style="color: #66cc66;">{</span><br />   <span style="color: #0000ff;">$output</span> .= <span style="color: #ff0000;">'&lt;span class="my-stock-quote"&gt;'</span>.<span style="color: #0000ff;">$my_stock</span><span style="color: #66cc66;">[</span><span style="color: #cc66cc;">0</span><span style="color: #66cc66;">]</span>.<span style="color: #ff0000;">' '</span>.<span style="color: #0000ff;">$my_stock</span><span style="color: #66cc66;">[</span><span style="color: #cc66cc;">2</span><span style="color: #66cc66;">]</span>.<span style="color: #ff0000;">' '</span>.<span style="color: #0000ff;">$my_stock</span><span style="color: #66cc66;">[</span><span style="color: #cc66cc;">3</span><span style="color: #66cc66;">]</span>.<span style="color: #ff0000;">' '</span>.<span style="color: #0000ff;">$my_stock</span><span style="color: #66cc66;">[</span><span style="color: #cc66cc;">8</span><span style="color: #66cc66;">]</span>.<span style="color: #ff0000;">' '</span>.<span style="color: #0000ff;">$my_stock</span><span style="color: #66cc66;">[</span><span style="color: #cc66cc;">9</span><span style="color: #66cc66;">]</span>.<span style="color: #ff0000;">' EST&lt;/span&gt;'</span>;<br /><span style="color: #66cc66;">}</span><br /><span style="color: #b1b100;">return</span> <span style="color: #0000ff;">$output</span>;</div></div> <p>I wound up throwing a variant of the snippet above into a block created in my module, allowing me to then jump to panels and throw the output wherever I wanted.</p> <p>Now I know... the example is somewhat contrived. There is no "secret" here. Everything outlined in this post would quickly be revealed by only a few minutes of looking through the Stock API module. I wanted to demonstrate that by making a simple call to the Stock API I was able to save significant time in themeing, as well as gain flexibility in function. The alternative would be wrangling the output of the Stock module's block, and making concessions for it's functional behavior. It is easy to fall into the thinking that Drupal site building is just finding a module that is a closest fit and wrestling with what it gives us. Indeed sometimes that may be the case (Organic Groups comes to mind), but to follow that line of thinking blindly is most certainly a mistake.</p></div></div></div> Fri, 16 Sep 2011 04:28:56 +0000 rho 154 at http://theoleschool.com