<?xml version="1.0"?>
<News hasArchived="true" page="7909" pageCount="10826" pageSize="10" timestamp="Sun, 20 Sep 2026 20:59:17 -0400" url="https://my3.my.umbc.edu/posts.xml?mode=activity&amp;page=7909&amp;range=2">
<NewsItem contentIssues="false" id="40914" important="false" status="posted" url="https://my3.my.umbc.edu/posts/40914">
<Title>Language Issues SIG Dissertation Award 2014</Title>
<Body>
<![CDATA[
    <div class="html-content">
    <p>
    <span>The Comparative International Education Society (CIES) Language Issues (LI)
     Special Interest Group (SIG) is pleased to announce its 2014 
    Dissertation Award!  The award is open to dissertations that focus on 
    educational language issues, broadly defined, in comparative and 
    international education.  Dissertations will be reviewed based on the 
    following criteria:</span></p>
    <div>
    <span> </span><br>
    </div>
    <p>
    <span>1.</span> <span>The dissertation’s relevance to current thinking in the field</span></p>
    <p><span>2.</span> <span>The degree of sophistication or innovation of the methodology used</span></p>
    <p><span>3.</span> <span>The soundness of data collection and analysis</span></p>
    <p><span>4.</span> <span>The dissertation’s social utility and/or its implication for policy</span></p>
    <p><span>5.</span> <span>The degree to which the dissertation incorporates a comparative perspective</span></p>
    <div>
    <span> </span><br>
    </div>
    <p>
    <span>Eligible
     dissertations must have been filed within an 18-month period prior to 
    the LI SIG award’s extended submission deadline of <span><span>February 15, 2014</span></span> (e.g., from <span><span>August 15, 2012 to February 15, 2014</span></span>).  
     Students in Ed.D. or Ph.D. programs are eligible and the applicant 
    and/or the Dissertation Chair must be a current member of CIES.  The 
    winning dissertation will receive $100.</span></p>
    <p><strong><span>Applicants should submit the following four documents to be considered for the award:</span></strong></p>
    <div>
    <span> </span><br>
    </div>
    <p>
    <span>1.</span> <span>A
     detailed abstract of no more than five pages along with a 
    self-assessment indicating how the dissertation meets the above five 
    criteria; both should be doubled-spaced with no author identification 
    (for blind review)</span></p>
    <p><span>2.</span> <span>A
     title page (not counted within the page limit) with author’s name, 
    address, institutional affiliation, telephone, e-mail address, and names
     of dissertation committee chair(s) and members</span></p>
    <p><span>3.</span> <span>A reference page (not counted within the page limit) with only the references cited in the abstract, and</span></p>
    <p><span>4.</span> <span>One
     letter from the Dissertation Chair recommending the dissertation for 
    this award (this letter may be sent directly to the Committee at the 
    e-mail address below).</span><br></p>
    <p><span>After
     the initial round of consideration, the two highest rated candidates 
    will be asked to send the Committee a file with their entire 
    dissertation. Awardees will be recognized formally at the LI SIG 
    business meeting at the CIES Conference in Toronto, Canada, <span><span>March 10-14, 2014</span></span>.</span></p>
    <div>
    <span> </span><br>
    </div>
    <p>
    <span>Submission Information</span></p>
    <p><span>Please,
     submit the above documents electronically (as attachments in Word 
    compatible files) with the key word “Dissertation Award Submission” in 
    the subject line to:  </span><span><a href="mailto:cieslangsig@gmail.com" rel="nofollow external" class="bo">cieslangsig@gmail.com</a></span><span> </span><span>by <span><span>February 15, 2014</span></span>.  If you have any questions, please do not hesitate to contact Awards Committee at the above e-mail address.</span></p>
    </div>
]]>
</Body>
<Summary>The Comparative International Education Society (CIES) Language Issues (LI)  Special Interest Group (SIG) is pleased to announce its 2014  Dissertation Award!  The award is open to dissertations...</Summary>
<TrackingUrl>https://my3.my.umbc.edu/api/v0/pixel/news/40914/guest@my.umbc.edu/3d7b547cbd2959e66b92d461e5c5d634/api/pixel</TrackingUrl>
<Group token="llc">Language, Literacy and Culture Doctoral Program</Group>
<GroupUrl>https://my3.my.umbc.edu/groups/llc</GroupUrl>
<AvatarUrl>https://assets4-my.umbc.edu/system/shared/avatars/groups/000/000/537/e594b22cf15b445f7476775aa508e9c3/xsmall.png?1375383725</AvatarUrl>
<AvatarUrl size="original">https://assets4-my.umbc.edu/system/shared/avatars/groups/000/000/537/e594b22cf15b445f7476775aa508e9c3/original.jpg?1375383725</AvatarUrl>
<AvatarUrl size="xxlarge">https://assets3-my.umbc.edu/system/shared/avatars/groups/000/000/537/e594b22cf15b445f7476775aa508e9c3/xxlarge.png?1375383725</AvatarUrl>
<AvatarUrl size="xlarge">https://assets1-my.umbc.edu/system/shared/avatars/groups/000/000/537/e594b22cf15b445f7476775aa508e9c3/xlarge.png?1375383725</AvatarUrl>
<AvatarUrl size="large">https://assets4-my.umbc.edu/system/shared/avatars/groups/000/000/537/e594b22cf15b445f7476775aa508e9c3/large.png?1375383725</AvatarUrl>
<AvatarUrl size="medium">https://assets1-my.umbc.edu/system/shared/avatars/groups/000/000/537/e594b22cf15b445f7476775aa508e9c3/medium.png?1375383725</AvatarUrl>
<AvatarUrl size="small">https://assets1-my.umbc.edu/system/shared/avatars/groups/000/000/537/e594b22cf15b445f7476775aa508e9c3/small.png?1375383725</AvatarUrl>
<AvatarUrl size="xsmall">https://assets4-my.umbc.edu/system/shared/avatars/groups/000/000/537/e594b22cf15b445f7476775aa508e9c3/xsmall.png?1375383725</AvatarUrl>
<AvatarUrl size="xxsmall">https://assets2-my.umbc.edu/system/shared/avatars/groups/000/000/537/e594b22cf15b445f7476775aa508e9c3/xxsmall.png?1375383725</AvatarUrl>
<Sponsor>Language, Literacy and Culture Doctoral Program</Sponsor>
<PawCount>0</PawCount>
<CommentCount>0</CommentCount>
<CommentsAllowed>true</CommentsAllowed>
<PostedAt>Mon, 03 Feb 2014 09:43:54 -0500</PostedAt>
<EditAt>Mon, 03 Feb 2014 09:44:32 -0500</EditAt>
</NewsItem>

<NewsItem contentIssues="false" id="40916" important="false" status="posted" url="https://my3.my.umbc.edu/posts/40916">
<Title>Standards for Web Applications on Mobile: current state and roadmap</Title>
<Body>
<![CDATA[
    <div class="html-content">
    <p><a href="http://www.w3.org/2013/02/mobile-web-app-state/" rel="nofollow external" class="bo"><img src="http://www.w3.org/2011/05/webapp-sm.png" alt="Thumbnail of application platform diagram that appears in the report" style="max-width: 100%; height: auto;"></a>W3C has published the January 2014 edition of <a href="http://www.w3.org/2014/01/mobile-web-app-state/" rel="nofollow external" class="bo">Standards for Web Applications on Mobile</a>, an overview of the various technologies developed in W3C that increase the capabilities of Web applications, and how they apply more specifically to the mobile context.</p>
    <p>A deliverable of the <a href="http://html5apps-project.eu/" rel="nofollow external" class="bo">HTML5Apps</a> project, this edition of the document includes changes and additions since September 2013, including 6 documents reaching Recommendation status (a record), which shows increased maturity of the platform; 4 <abbr>FPWD</abbr> and 4 new editors drafts illustrate it is still growing up nicely; and a lot of the changes are linked to performance, off-line support and packaging.</p>
    <p>Learn more about the <a href="http://www.w3.org/Mobile/IG/" rel="nofollow external" class="bo">Web and Mobile Interest Group</a>.</p>
    </div>
]]>
</Body>
<Summary>W3C has published the January 2014 edition of Standards for Web Applications on Mobile, an overview of the various technologies developed in W3C that increase the capabilities of Web applications,...</Summary>
<Website>http://www.w3.org/blog/news/archives/3623</Website>
<TrackingUrl>https://my3.my.umbc.edu/api/v0/pixel/news/40916/guest@my.umbc.edu/8f377e73453c2730ecdeff0b2cb9e087/api/pixel</TrackingUrl>
<Tag>browsers-and-authoring-tools</Tag>
<Tag>css</Tag>
<Tag>design</Tag>
<Tag>development</Tag>
<Tag>home-page-stories</Tag>
<Tag>html</Tag>
<Tag>javascript</Tag>
<Tag>mysql</Tag>
<Tag>sql</Tag>
<Tag>w3</Tag>
<Tag>web</Tag>
<Tag>web-design-and-applications</Tag>
<Tag>web-of-devices</Tag>
<Tag>web-of-services</Tag>
<Group token="retired-583">Web Developer - Build Group</Group>
<GroupUrl>https://my3.my.umbc.edu/groups/retired-583</GroupUrl>
<AvatarUrl>https://assets3-my.umbc.edu/system/shared/avatars/groups/000/000/583/fc60f5d7abc2e080599bb6dc465db54d/xsmall.png?1363101197</AvatarUrl>
<AvatarUrl size="original">https://assets4-my.umbc.edu/system/shared/avatars/groups/000/000/583/fc60f5d7abc2e080599bb6dc465db54d/original.jpg?1363101197</AvatarUrl>
<AvatarUrl size="xxlarge">https://assets4-my.umbc.edu/system/shared/avatars/groups/000/000/583/fc60f5d7abc2e080599bb6dc465db54d/xxlarge.png?1363101197</AvatarUrl>
<AvatarUrl size="xlarge">https://assets4-my.umbc.edu/system/shared/avatars/groups/000/000/583/fc60f5d7abc2e080599bb6dc465db54d/xlarge.png?1363101197</AvatarUrl>
<AvatarUrl size="large">https://assets3-my.umbc.edu/system/shared/avatars/groups/000/000/583/fc60f5d7abc2e080599bb6dc465db54d/large.png?1363101197</AvatarUrl>
<AvatarUrl size="medium">https://assets3-my.umbc.edu/system/shared/avatars/groups/000/000/583/fc60f5d7abc2e080599bb6dc465db54d/medium.png?1363101197</AvatarUrl>
<AvatarUrl size="small">https://assets4-my.umbc.edu/system/shared/avatars/groups/000/000/583/fc60f5d7abc2e080599bb6dc465db54d/small.png?1363101197</AvatarUrl>
<AvatarUrl size="xsmall">https://assets3-my.umbc.edu/system/shared/avatars/groups/000/000/583/fc60f5d7abc2e080599bb6dc465db54d/xsmall.png?1363101197</AvatarUrl>
<AvatarUrl size="xxsmall">https://assets4-my.umbc.edu/system/shared/avatars/groups/000/000/583/fc60f5d7abc2e080599bb6dc465db54d/xxsmall.png?1363101197</AvatarUrl>
<Sponsor>Web Developer - Build Group</Sponsor>
<PawCount>0</PawCount>
<CommentCount>0</CommentCount>
<CommentsAllowed>true</CommentsAllowed>
<PostedAt>Mon, 03 Feb 2014 09:30:13 -0500</PostedAt>
</NewsItem>

<NewsItem contentIssues="false" id="40917" important="false" status="posted" url="https://my3.my.umbc.edu/posts/40917">
<Title>UPDATE: Daytime classes at our Columbia location are now cancelled. Sonography c...</Title>
<Body>
<![CDATA[
    <div class="html-content">UPDATE: Daytime classes at our Columbia location are now cancelled. Sonography classes are running as scheduled.</div>
]]>
</Body>
<Summary>UPDATE: Daytime classes at our Columbia location are now cancelled. Sonography classes are running as scheduled.</Summary>
<Website>http://www.facebook.com/umbctraining/posts/10151839859816076</Website>
<TrackingUrl>https://my3.my.umbc.edu/api/v0/pixel/news/40917/guest@my.umbc.edu/9ad22fc4580e494383af750f0bc89ba2/api/pixel</TrackingUrl>
<Tag>ccna</Tag>
<Tag>ceh</Tag>
<Tag>centers</Tag>
<Tag>cisco</Tag>
<Tag>cyber</Tag>
<Tag>cybersecurity</Tag>
<Tag>information</Tag>
<Tag>it</Tag>
<Tag>leadership</Tag>
<Tag>management</Tag>
<Tag>microsoft</Tag>
<Tag>project</Tag>
<Tag>security</Tag>
<Tag>technology</Tag>
<Tag>training</Tag>
<Tag>umbc</Tag>
<Group token="retired-575">UMBC Training Centers</Group>
<GroupUrl>https://my3.my.umbc.edu/groups/retired-575</GroupUrl>
<AvatarUrl>https://assets1-my.umbc.edu/system/shared/avatars/groups/000/000/575/83756b985266168d0d29c6c9a146db50/xsmall.png?1361981335</AvatarUrl>
<AvatarUrl size="original">https://assets4-my.umbc.edu/system/shared/avatars/groups/000/000/575/83756b985266168d0d29c6c9a146db50/original.jpg?1361981335</AvatarUrl>
<AvatarUrl size="xxlarge">https://assets3-my.umbc.edu/system/shared/avatars/groups/000/000/575/83756b985266168d0d29c6c9a146db50/xxlarge.png?1361981335</AvatarUrl>
<AvatarUrl size="xlarge">https://assets4-my.umbc.edu/system/shared/avatars/groups/000/000/575/83756b985266168d0d29c6c9a146db50/xlarge.png?1361981335</AvatarUrl>
<AvatarUrl size="large">https://assets1-my.umbc.edu/system/shared/avatars/groups/000/000/575/83756b985266168d0d29c6c9a146db50/large.png?1361981335</AvatarUrl>
<AvatarUrl size="medium">https://assets2-my.umbc.edu/system/shared/avatars/groups/000/000/575/83756b985266168d0d29c6c9a146db50/medium.png?1361981335</AvatarUrl>
<AvatarUrl size="small">https://assets4-my.umbc.edu/system/shared/avatars/groups/000/000/575/83756b985266168d0d29c6c9a146db50/small.png?1361981335</AvatarUrl>
<AvatarUrl size="xsmall">https://assets1-my.umbc.edu/system/shared/avatars/groups/000/000/575/83756b985266168d0d29c6c9a146db50/xsmall.png?1361981335</AvatarUrl>
<AvatarUrl size="xxsmall">https://assets2-my.umbc.edu/system/shared/avatars/groups/000/000/575/83756b985266168d0d29c6c9a146db50/xxsmall.png?1361981335</AvatarUrl>
<Sponsor>UMBC Training Centers</Sponsor>
<PawCount>0</PawCount>
<CommentCount>0</CommentCount>
<CommentsAllowed>true</CommentsAllowed>
<PostedAt>Mon, 03 Feb 2014 09:22:46 -0500</PostedAt>
<EditAt>Mon, 03 Feb 2014 09:22:46 -0500</EditAt>
</NewsItem>

<NewsItem contentIssues="false" id="40912" important="false" status="posted" url="https://my3.my.umbc.edu/posts/40912">
<Title>Grants to Students for Creative Work, Scholarship, Research</Title>
<Tagline>TODAY. All Majors</Tagline>
<Body>
<![CDATA[
    <div class="html-content">Do you have an idea for independent creative work, scholarship, or research? You can apply NOW for up to $1,500 to fund your work.<br><br>Learn how to create a strong proposal for an<br>Undergraduate Research Award (URA)<br><strong>TODAY, Monday, February 3, at 12:00 in Sherman 108</strong><br><br>Come to find out whether this is for you. Hear about <br><ul>
    <li>Defining the project/research question</li>
    <li>Writing a budget</li>
    <li>Working with a faculty mentor</li>
    </ul>
    <p>Application deadline SOON.<br></p>
    <p>If you think you will be applying for a URA, e-mail <a href="mailto:mcglynn@umbc.edu">mcglynn@umbc.edu</a> to be included on the list of interested students. <br></p>
    <p><br></p>
    </div>
]]>
</Body>
<Summary>Do you have an idea for independent creative work, scholarship, or research? You can apply NOW for up to $1,500 to fund your work.  Learn how to create a strong proposal for an Undergraduate...</Summary>
<Website>http://umbc.edu/undergrad_ed/research/URA/</Website>
<TrackingUrl>https://my3.my.umbc.edu/api/v0/pixel/news/40912/guest@my.umbc.edu/d55b433ffb52f9ca282c829fc7f75dac/api/pixel</TrackingUrl>
<Group token="undergradresearch">Undergraduate Research</Group>
<GroupUrl>https://my3.my.umbc.edu/groups/undergradresearch</GroupUrl>
<AvatarUrl>https://assets2-my.umbc.edu/system/shared/avatars/groups/000/000/006/875606ced2b629148af4caa1a4e8dd3c/xsmall.png?1600355057</AvatarUrl>
<AvatarUrl size="original">https://assets3-my.umbc.edu/system/shared/avatars/groups/000/000/006/875606ced2b629148af4caa1a4e8dd3c/original.jpg?1600355057</AvatarUrl>
<AvatarUrl size="xxlarge">https://assets4-my.umbc.edu/system/shared/avatars/groups/000/000/006/875606ced2b629148af4caa1a4e8dd3c/xxlarge.png?1600355057</AvatarUrl>
<AvatarUrl size="xlarge">https://assets1-my.umbc.edu/system/shared/avatars/groups/000/000/006/875606ced2b629148af4caa1a4e8dd3c/xlarge.png?1600355057</AvatarUrl>
<AvatarUrl size="large">https://assets2-my.umbc.edu/system/shared/avatars/groups/000/000/006/875606ced2b629148af4caa1a4e8dd3c/large.png?1600355057</AvatarUrl>
<AvatarUrl size="medium">https://assets2-my.umbc.edu/system/shared/avatars/groups/000/000/006/875606ced2b629148af4caa1a4e8dd3c/medium.png?1600355057</AvatarUrl>
<AvatarUrl size="small">https://assets4-my.umbc.edu/system/shared/avatars/groups/000/000/006/875606ced2b629148af4caa1a4e8dd3c/small.png?1600355057</AvatarUrl>
<AvatarUrl size="xsmall">https://assets2-my.umbc.edu/system/shared/avatars/groups/000/000/006/875606ced2b629148af4caa1a4e8dd3c/xsmall.png?1600355057</AvatarUrl>
<AvatarUrl size="xxsmall">https://assets4-my.umbc.edu/system/shared/avatars/groups/000/000/006/875606ced2b629148af4caa1a4e8dd3c/xxsmall.png?1600355057</AvatarUrl>
<Sponsor>Undergraduate Research</Sponsor>
<PawCount>0</PawCount>
<CommentCount>0</CommentCount>
<CommentsAllowed>true</CommentsAllowed>
<PostedAt>Mon, 03 Feb 2014 09:16:00 -0500</PostedAt>
</NewsItem>

<NewsItem contentIssues="true" id="41053" important="false" status="posted" url="https://my3.my.umbc.edu/posts/41053">
<Title>Creating Brackets Extensions</Title>
<Body>
<![CDATA[
    <div class="html-content">
    <p>A little while ago I wrote about the <a href="http://dev.tutsplus.com/tutorials/deeper-in-the-brackets-editor--net-35527" rel="nofollow external" class="bo">recent updates</a> to the Brackets editor. <a href="https://github.com/adobe/brackets" rel="nofollow external" class="bo">Brackets</a> is an open source project focused on web standards and built with web technologies. It has a narrow focus and therefore may not have a particular feature you've come to depend upon. Luckily, Brackets ships with a powerful extension API that lets you add any number of new features. In this article, I'm going to discuss this API and demonstrate how you can build your own extensions.</p>
    <p></p>
    <p>It is <em>crucial</em> that you remember that Brackets is in active development. This article is being written in December of 2013. It is certainly possible that the code demonstrated below is now out of date. Keep that in mind and be sure to check the <a href="https://github.com/adobe/brackets/wiki/How-to-Write-Extensions" rel="nofollow external" class="bo">wiki</a> for the latest updates to the extension API.</p>
    <hr>
    <h2>Getting Started</h2>
    <p>I'm going to assume you read my last article and are already familiar with the extension manager. This provides a simple, one click method of installing extensions. One of the best ways you can learn to write extensions is by looking at the work done by others (that's how I learned). I'd recommend grabbing a few extensions (there's almost 200 available now) and tearing apart their code. Don't be afraid to break a few while you're at it.</p>
    <p>Brackets puts all installed extensions within one main folder. To find that folder, go to the <strong>Help</strong> menu and select "<strong>Show Extensions Folder</strong>". For my OS X install, this was located at <strong>/Users/ray/Library/Application Support/Brackets/extensions/user</strong>. If you go up from that folder, you'll notice a disabled folder as well. Brackets will make a valiant effort to load no matter what, but if you ever find yourself in a situation where Brackets has completely crapped the bed and simply will not work, consider moving potentially bad extensions into the disabled folder. Later on in the article, I'll discuss how you can monitor and debug extensions to help prevent such problems in the first place.</p>
    <p>Begin by going to your user folder and creating a new folder, <code>helloworld1</code>. Yes, even though it is completely lame, we're going to build a HelloWorld extension. Don't hate me, I like simple. Inside that folder create a new file called <code>main.js</code>. Listing one shows what the contents of this file should be. Note that in this article I'll go through a couple of different iterations of the <code>helloworld</code> extension. Each one will be named with a progressively higher number. So our first example is from <code>helloworld1</code>, the next <code>helloworld2</code>, and so on. It would make sense for you to simply copy the code into one folder, <code>helloworld</code>, instead of copying each one by themselves. If you do, you'll have multiple related extensions running at once and that can definitely confuse things.</p>
    <pre>Listing 1: helloworld1/main.js&#x000A;    define(function(require, exports, module) {&#x000A;    &#x000A;        function log(s) {&#x000A;                console.log("[helloworld] "+s);&#x000A;        }&#x000A;    &#x000A;        log("Hello from HelloWorld.");&#x000A;    });</pre>
    <p>The first line defines our extension as a module that will be picked up by Brackets automatically on application load. The rest of the extension is a custom log message (you will see why in a second) and a call to that logger. Once you have this file saved, go back to Brackets, select the Debug menu, and hit Reload. (You can also use <strong>Command/Control+R</strong> to reload as well.) </p>
    <p>Brackets will reload and ... nothing else will happen. The extension we built didn't actually do anything that we could see, but it did log to the console. But where is that console? Brackets provides an easy way to view the console. Simply go back to the <strong>Debug</strong> menu and select <strong>Show Developer Tools</strong>. This will open a new tab in Chrome with a familiar Dev Tools UI. In the screen shot below I've highlighted our log. Other extensions, and Brackets itself, will also log messages to this screen. By prefixing my log messages with <code>[helloworld]</code>, I can make my own stuff a bit easier to find.</p>
    
    <img src="http://cdn.tutsplus.com/net/uploads/2014/01/s1.jpeg" alt="Brackets console messages" width="600" height="268" style="max-width: 100%; height: auto;"><br>
    
    <p>Note that the full Chrome <code>console.api</code> works here. You can do stuff like this to format your console messages:</p>
    <pre>    &#x000A;    function log(s) {&#x000A;        console.log("%c[helloworld] "+s,"color:blue;font-size:large");&#x000A;    }</pre>
    <p>Go crazy, but try to remove these messages before you share your code with the rest of the world. In case you're curious, you can't use dev tools in another browser, like Firefox, at this time.</p>
    <hr>
    <h2>Integration Points</h2>
    <p>Now that you know the (very) basics, let's talk about what Brackets extensions can do to the editor:</p>
    <ul>
    <li>They can create keyboard shortcuts, allowing them to respond to custom keystrokes.</li>
    <li>They can add to the top level menu.</li>
    <li>They can add context menus (and to a specific area, like the file listing or the editor window).</li>
    <li>They can create UI items. This can be a modal dialog or even a panel. (Currently the panel is locked to the bottom of the screen).</li>
    <li>They can create a linting provider (essentially they can register themselves as a code checker for a file type).</li>
    <li>They can create their own inline editors (a major feature of Brackets).</li>
    <li>They can register as a documentation provider (for example, adding MDN support for docs).</li>
    <li>They can integrate with Quick Find and Quick Open.</li>
    <li>They can add custom code hints and syntax colors.</li>
    <li>They can read the current file open in the editor as well as modify it. (They can also see the current selected text, if any.)</li>
    </ul>
    <p>That describes how extensions can modify Brackets, but what can extensions actually do in terms of code? Keeping in mind that you're writing extensions in pure web standards (HTML, JavaScript, and CSS), you actually have quite a bit of power. The only real limits relate to binary data. There is a File system API that gives you control over files but is limited to text data only. Luckily, you have a way out.</p>
    <p>Any Brackets extension can integrate with Node.js. If you've got an existing Node.js package your extension can make calls to it and do, well, whatever Node.js can do, which is essentially anything.</p>
    <p>Let's update our extension to integrate with the editor a bit better. I'll start by simply adding a menu item for the extension.</p>
    <pre>Listing 2: helloworld2/main.js&#x000A;    /*&#x000A;    Based - in part - on the HelloWorld sample extension on the Brackets wiki:&#x000A;    <a href="https://github.com/adobe/brackets/wiki/Simple-%22Hello-World%22-extension">https://github.com/adobe/brackets/wiki/Simple-%22Hello-World%22-extension</a>&#x000A;    */&#x000A;    define(function(require, exports, module) {&#x000A;    &#x000A;        var CommandManager = brackets.getModule("command/CommandManager"),&#x000A;                Menus = brackets.getModule("command/Menus"),&#x000A;                AppInit = brackets.getModule("utils/AppInit");&#x000A;    &#x000A;    &#x000A;        function log(s) {&#x000A;                console.log("[helloworld2] "+s);&#x000A;        }&#x000A;    &#x000A;    &#x000A;        function handleHelloWorld() {&#x000A;                alert("You ran me, thanks!");&#x000A;        }&#x000A;    &#x000A;    &#x000A;        AppInit.appReady(function () {&#x000A;    &#x000A;                log("Hello from HelloWorld2.");&#x000A;    &#x000A;                var HELLOWORLD_EXECUTE = "helloworld.execute";&#x000A;    &#x000A;                CommandManager.register("Run HelloWorld", HELLOWORLD_EXECUTE, handleHelloWorld);&#x000A;    &#x000A;                var menu = Menus.getMenu(Menus.AppMenuBar.VIEW_MENU);&#x000A;                menu.addMenuItem(HELLOWORLD_EXECUTE);&#x000A;    &#x000A;        });&#x000A;    &#x000A;    });</pre>
    <p>We've got a few changes here so let's tackle them one by one. You'll notice that the extension begins with three calls to brackets.getModule. All extensions have access to a brackets object that provides an API where we can load in core functionality from the editor. In this case the extension has loaded two libraries we'll need for the menu (CommandManager and Menus) and one which will be used to help initialize the extension (AppInit). </p>
    <p>Lets talk about AppInit. You can see that most of the extension is now loaded with a appReady callback. This callback is fired when Brackets has completed loading and is generally considered "best practice" for extensions to make use of. </p>
    <p>Registering a menu item takes a few steps. I begin by defining a "command ID", a unique identifier for the item I'll be adding to the UI. The typical way to do this is with the format <code>extensionname.someaction</code>. In my case, I used <code>helloworld.execute</code>. I can then register this command along with the function (<code>handleHelloWorld</code>) that should be called when the command is fired.</p>
    <p>The final step is to add this command to the menu. You can probably guess that my menu item will be added under the View menu based on this value: Menus.AppMenuBar.VIEW_MENU. How did I know that value? Simple, I saw other extensions do it. Seriously though, there is no specific list of items like this yet. Don't forget that Brackets is open source. I can easily pop over to the GitHub repo and check it out. In this case, the file is <code>Menus.js</code>, located on <a href="https://github.com/adobe/brackets/blob/master/src/command/Menus.js" rel="nofollow external" class="bo">Github</a>. In there I can see where the various different core menus are defined:</p>
    <pre>/**&#x000A;      * Brackets Application Menu Constants&#x000A;      * @enum {string}&#x000A;    */&#x000A;    var AppMenuBar = {&#x000A;         FILE_MENU       : "file-menu",&#x000A;         EDIT_MENU       : "edit-menu",&#x000A;         VIEW_MENU       : "view-menu",&#x000A;         NAVIGATE_MENU   : "navigate-menu",&#x000A;         HELP_MENU       : "help-menu"&#x000A;    };</pre>
    <p>As a general rule of thumb, it makes sense to have at least a cursory understanding of what's available in Brackets itself. Your extensions will, from time to time, make use of multiple different features so it's definitely in your best interest to at least know the lay of the land. </p>
    <p>After reloading Brackets, you'll now see the menu item in the <strong>View</strong> menu. Exactly where it is may be a bit random as you may have other extensions installed.</p>
    
    <img src="http://cdn.tutsplus.com/net/uploads/2014/01/s2.png" alt="View menu updated" width="600" height="447" style="max-width: 100%; height: auto;"><br>
    
    <p>You can actually be a bit more specific about your position. Again, this is where the source code will help you. The same file I linked to above also contains the addMenuItem definition. </p>
    <hr>
    <h2>Put Some Lipstick on That Pig</h2>
    <p>Now that you've seen a simple example of how an extension can integrate into Brackets, let's look at how we update the UI. In the previous version of our code, an alert was used to send a message. While this works, it isn't very pretty. Your code can access the Brackets editor just like any other DOM modification code. While you <em>can</em> do anything you want, there are a few standard ways extensions update the UI in Brackets. (As a warning, in general you do not want to touch the DOM of the main editor UI. You can, but with future updates, your code may break. Also, users may not be happy if your extension changes something core to Brackets.)</p>
    <p>The first method we'll look at uses modal dialogs. Brackets already uses this and has an API available for extensions to call. As a simple example, let's just update the HelloWorld extension to use a modal instead.</p>
    <pre>Listing 3: helloworld3/main.js&#x000A;    /*&#x000A;    Based - in part - on the HelloWorld sample extension on the Brackets wiki:&#x000A;    <a href="https://github.com/adobe/brackets/wiki/Simple-%22Hello-World%22-extension">https://github.com/adobe/brackets/wiki/Simple-%22Hello-World%22-extension</a>&#x000A;    */&#x000A;    define(function(require, exports, module) {&#x000A;    &#x000A;        var CommandManager = brackets.getModule("command/CommandManager"),&#x000A;            Menus = brackets.getModule("command/Menus"),&#x000A;            Dialogs = brackets.getModule("widgets/Dialogs"),&#x000A;            DefaultDialogs = brackets.getModule("widgets/DefaultDialogs"),&#x000A;            AppInit = brackets.getModule("utils/AppInit");&#x000A;    &#x000A;        function log(s) {&#x000A;                console.log("[helloworld3] "+s);&#x000A;        }&#x000A;    &#x000A;        function handleHelloWorld() {&#x000A;            Dialogs.showModalDialog(DefaultDialogs.DIALOG_ID_INFO, "Hello World", "Same Important Message");&#x000A;        }&#x000A;    &#x000A;        AppInit.appReady(function () {&#x000A;    &#x000A;            log("Hello from HelloWorld3.");&#x000A;    &#x000A;            var HELLOWORLD_EXECUTE = "helloworld.execute";&#x000A;    &#x000A;            CommandManager.register("Run HelloWorld", HELLOWORLD_EXECUTE, handleHelloWorld);&#x000A;    &#x000A;            var menu = Menus.getMenu(Menus.AppMenuBar.VIEW_MENU);&#x000A;            menu.addMenuItem(HELLOWORLD_EXECUTE);&#x000A;    &#x000A;        });&#x000A;    &#x000A;    });</pre>
    <p>Note the addition of two additional Brackets modules: <code>Dialogs</code> and <code>DefaultDialogs</code>. The next change is in <code>handleHelloWorld</code>. One of the methods in the Dialog library is the ability to show a dialog (no surprise there, I suppose). The method wants a class, a title, and a body, and that's it. There's more you can do with dialogs, but for now, this demonstrates the feature. Now when we run the command, we get a much prettier UI. (Along with default buttons and behaviours to handle closing the dialog.)</p>
    
    <img src="http://cdn.tutsplus.com/net/uploads/2014/01/s3.png" alt="Dialog example" width="600" height="397" style="max-width: 100%; height: auto;"><br>
    
    <p>That's one example, now lets look at another: creating a bottom panel. As with dialogs, we've got support from Brackets to make it easier. Let's look at an example and then I'll explain the changes.</p>
    <pre>Listing 4: helloworld4/main.js&#x000A;    /*&#x000A;    Based - in part - on the HelloWorld sample extension on the Brackets wiki:&#x000A;    <a href="https://github.com/adobe/brackets/wiki/Simple-%22Hello-World%22-extension">https://github.com/adobe/brackets/wiki/Simple-%22Hello-World%22-extension</a>&#x000A;    */&#x000A;    define(function(require, exports, module) {&#x000A;    &#x000A;        var CommandManager = brackets.getModule("command/CommandManager"),&#x000A;        Menus = brackets.getModule("command/Menus"),&#x000A;        PanelManager = brackets.getModule("view/PanelManager"),&#x000A;        AppInit = brackets.getModule("utils/AppInit");&#x000A;    &#x000A;        var HELLOWORLD_EXECUTE = "helloworld.execute";&#x000A;        var panel;&#x000A;    &#x000A;        function log(s) {&#x000A;                console.log("[helloworld4] "+s);&#x000A;        }&#x000A;    &#x000A;        function handleHelloWorld() {&#x000A;            if(panel.isVisible()) {&#x000A;                panel.hide();&#x000A;                CommandManager.get(HELLOWORLD_EXECUTE).setChecked(false);&#x000A;            } else {&#x000A;                panel.show();&#x000A;                CommandManager.get(HELLOWORLD_EXECUTE).setChecked(true);&#x000A;            }&#x000A;        }&#x000A;    &#x000A;        AppInit.appReady(function () {&#x000A;    &#x000A;                log("Hello from HelloWorld4.");&#x000A;    &#x000A;                CommandManager.register("Run HelloWorld", HELLOWORLD_EXECUTE, handleHelloWorld);&#x000A;    &#x000A;                var menu = Menus.getMenu(Menus.AppMenuBar.VIEW_MENU);&#x000A;                menu.addMenuItem(HELLOWORLD_EXECUTE);&#x000A;    &#x000A;                panel = PanelManager.createBottomPanel(HELLOWORLD_EXECUTE, $("&lt;div class='bottom-panel'&gt;HTML for my panel&lt;/div&gt;"),200);&#x000A;    &#x000A;        });&#x000A;    &#x000A;    });</pre>
    <p>Let's focus on the changes. First, I dropped the Dialog modules as I'm no longer using them. Instead, we load up the PanelManager. Down in the appReady block I've defined a new panel using the PanelManager API method createBottomPanel. Like the menu command this takes in a unique ID so I just reuse <code>HELLOWORLD_EXECUTE</code>. The second argument is a jQuery-wrapped block of HTML (and in case you're wondering, yes we can do this nicer), and finally, a minimum size. This sets up the panel but doesn't actually execute it.</p>
    <p>In the event handler, we have tied to the menu, we can ask the panel if it is visible and then either hide or show it. That part should be pretty trivial. For fun I've added in a bit more complexity. Notice that <code>CommandManager</code> lets us get a menu item and set a checked property. This may be unnecessary as the user can see the panel easily enough themselves, but adding the check just makes things a little bit more obvious. In the screen shot below you can see the panel in its visible state.</p>
    
    <img src="http://cdn.tutsplus.com/net/uploads/2014/01/s4.png" alt="Panel example" width="600" height="493" style="max-width: 100%; height: auto;"><br>
    
    <p>Right away you may be wondering about the panel HTML. Is there a better way to provide the HTML? Anyway to style it? Yep, lets look at a more advanced version.</p>
    <pre>Listing 5: helloworld5/main.js&#x000A;    /*&#x000A;    Based - in part - on the HelloWorld sample extension on the Brackets wiki:&#x000A;    <a href="https://github.com/adobe/brackets/wiki/Simple-%22Hello-World%22-extension">https://github.com/adobe/brackets/wiki/Simple-%22Hello-World%22-extension</a>&#x000A;    */&#x000A;    define(function(require, exports, module) {&#x000A;    &#x000A;    &#x000A;        var CommandManager = brackets.getModule("command/CommandManager"),&#x000A;        Menus = brackets.getModule("command/Menus"),&#x000A;        PanelManager = brackets.getModule("view/PanelManager"),&#x000A;        ExtensionUtils          = brackets.getModule("utils/ExtensionUtils"),        &#x000A;        AppInit = brackets.getModule("utils/AppInit");&#x000A;    &#x000A;        var HELLOWORLD_EXECUTE = "helloworld.execute";&#x000A;        var panel;&#x000A;        var panelHtml     = require("text!panel.html");&#x000A;    &#x000A;        function log(s) {&#x000A;                console.log("[helloworld5] "+s);&#x000A;        }&#x000A;    &#x000A;        function handleHelloWorld() {&#x000A;            if(panel.isVisible()) {&#x000A;                panel.hide();&#x000A;                CommandManager.get(HELLOWORLD_EXECUTE).setChecked(false);&#x000A;            } else {&#x000A;                panel.show();&#x000A;                CommandManager.get(HELLOWORLD_EXECUTE).setChecked(true);&#x000A;            }&#x000A;        }&#x000A;    &#x000A;        AppInit.appReady(function () {&#x000A;    &#x000A;            log("Hello from HelloWorld5.");&#x000A;            ExtensionUtils.loadStyleSheet(module, "helloworld.css");&#x000A;            CommandManager.register("Run HelloWorld", HELLOWORLD_EXECUTE, handleHelloWorld);&#x000A;    &#x000A;            var menu = Menus.getMenu(Menus.AppMenuBar.VIEW_MENU);&#x000A;            menu.addMenuItem(HELLOWORLD_EXECUTE);&#x000A;    &#x000A;            panel = PanelManager.createBottomPanel(HELLOWORLD_EXECUTE, $(panelHtml),200);&#x000A;    &#x000A;        });&#x000A;    &#x000A;    });</pre>
    <p>As before, I'm going to focus on the changes. First note that I've included a variable called <code>panelHtml</code> that is loaded via require. This lets me define my HTML outside of my JavaScript code. (You can also use templating engines. Brackets ships with Mustache.) The HTML behind the panel is rather simple.</p>
    <pre>Listing 6: helloworld5/panel.html&#x000A;    &lt;div class="bottom-panel helloworld-panel" id="helloworldPanel"&gt;&#x000A;    &lt;h1&gt;My Panel&lt;/h1&gt;&#x000A;    &#x000A;    &lt;p&gt;&#x000A;    My panel brings all the boys to the yard,&lt;br/&gt;&#x000A;    And they're like&lt;br/&gt;&#x000A;    It's better than yours,&lt;br/&gt;&#x000A;    Damn right it's better than yours,&lt;br/&gt;&#x000A;    I can teach you,&lt;br/&gt;&#x000A;    But I have to charge&#x000A;    &lt;/p&gt;&#x000A;    &lt;/div&gt;</pre>
    <p>Returning to <code>main.js</code>, I've demonstrated another feature, loadStyleSheet. This lets you load an extension specific style sheet. I created a file, <code>helloworld.css</code>, with some simple (but tasteful) CSS styles.</p>
    <pre>Listing 7: helloworld5/helloworld.css&#x000A;    .helloworld-panel h1 {&#x000A;            color: red;&#x000A;    }&#x000A;    &#x000A;    .helloworld-panel p {&#x000A;            color: blue;&#x000A;            font-weight: bold;&#x000A;    }</pre>
    <p>Note that I prefixed my styles with a unique name. This helps ensure my classes don't conflict with anything built into Brackets. With these simple changes my panel now looks much better, and you can see why I'm known world wide for my superior design skills. </p>
    
    <img src="http://cdn.tutsplus.com/net/uploads/2014/01/a5.png" alt="Epic CSS" width="600" height="396" style="max-width: 100%; height: auto;"><br>
    
    <hr>
    <h2>Packaging and Sharing Your Kick Butt Extension</h2>
    <p>Of course, just creating the coolest Brackets extension isn't quite enough. You probably (hopefully!) want to share it with others. One option is to just zip up the directory and put it on your website. Folks can download the zip, extract it, and copy it to their Brackets extensions folder. </p>
    <p>But that's not cool. You want to be cool, right? In order to share your extension and make it available via the Brackets Extension manager, you simply need to add a <code>package.json</code> file to your extension. If you've ever used Node.js, then this will seem familiar. Here is a sample one for our extension.</p>
    <pre>Listing 8: helloworld6/package.json&#x000A;    {&#x000A;        "name": "camden.helloworld",&#x000A;        "title": "HelloWorld",&#x000A;        "description": "Adds HelloWorld support to Brackets.",&#x000A;        "homepage": "<a href="https://github.com/cfjedimaster/something">https://github.com/cfjedimaster/something</a> real here",&#x000A;        "version": "1.0.0",&#x000A;        "author": "Raymond Camden &lt;<a href="mailto:raymondcamden@gmail.com">raymondcamden@gmail.com</a>&gt; (<a href="http://www.raymondcamden.com">http://www.raymondcamden.com</a>)",&#x000A;        "license": "MIT",&#x000A;        "engines": {&#x000A;            "brackets": "&lt;=0.34.0"&#x000A;        }&#x000A;    }</pre>
    <p>Most of this is self-explanatory, but the real crucial portion is the engines block. Brackets updates itself pretty rapidly. If Brackets added a particular feature at some point that your extension relies on, you can add a simple conditional here to ensure folks don't try to install your extension on an incompatible version. (You can find a full listing of the possible settings on <a href="https://github.com/adobe/brackets/wiki/Extension-package-format#packagejson-format" rel="nofollow external" class="bo">the Wiki</a>.)</p>
    <p>Once you've done this, the next part is to upload it to the <a href="https://brackets-registry.aboutweb.com/" rel="nofollow external" class="bo">Brackets Registry</a>. You will need to log in via your GitHub account, but once you've done that, you can then simply upload your zip. Your extension will then be available to anyone using Brackets. Even better, if you update your extension, the Extension Manager will actually be able to flag this to the user so they know an update is available.</p>
    <hr>
    <h2>What Else?</h2>
    <p>Hopefully, you've seen how easy it is to extend Brackets. There's more we didn't cover, like the <a href="http://blog.brackets.io/2013/10/07/new-linting-api/" rel="nofollow external" class="bo">Linting API</a> and <a href="https://github.com/adobe/brackets/wiki/Brackets-Node-Process:-Overview-for-Developers#usage-example" rel="nofollow external" class="bo">NodeJS integration</a>, but this article should be more than enough to get you started. As a reminder, do not forget there is a large collection of extensions available for you to start playing with right now. Good luck!</p>
    </div>
]]>
</Body>
<Summary>A little while ago I wrote about the recent updates to the Brackets editor. Brackets is an open source project focused on web standards and built with web technologies. It has a narrow focus and...</Summary>
<Website>http://code.tutsplus.com/tutorials/creating-brackets-extensions--net-36781</Website>
<TrackingUrl>https://my3.my.umbc.edu/api/v0/pixel/news/41053/guest@my.umbc.edu/cd0c6bab5ae7302ee4c242f13e87cce8/api/pixel</TrackingUrl>
<Tag>css</Tag>
<Tag>development</Tag>
<Tag>html</Tag>
<Tag>javascript</Tag>
<Tag>mysql</Tag>
<Tag>php</Tag>
<Tag>sql</Tag>
<Tag>wed</Tag>
<Group token="retired-583">Web Developer - Build Group</Group>
<GroupUrl>https://my3.my.umbc.edu/groups/retired-583</GroupUrl>
<AvatarUrl>https://assets3-my.umbc.edu/system/shared/avatars/groups/000/000/583/fc60f5d7abc2e080599bb6dc465db54d/xsmall.png?1363101197</AvatarUrl>
<AvatarUrl size="original">https://assets4-my.umbc.edu/system/shared/avatars/groups/000/000/583/fc60f5d7abc2e080599bb6dc465db54d/original.jpg?1363101197</AvatarUrl>
<AvatarUrl size="xxlarge">https://assets4-my.umbc.edu/system/shared/avatars/groups/000/000/583/fc60f5d7abc2e080599bb6dc465db54d/xxlarge.png?1363101197</AvatarUrl>
<AvatarUrl size="xlarge">https://assets4-my.umbc.edu/system/shared/avatars/groups/000/000/583/fc60f5d7abc2e080599bb6dc465db54d/xlarge.png?1363101197</AvatarUrl>
<AvatarUrl size="large">https://assets3-my.umbc.edu/system/shared/avatars/groups/000/000/583/fc60f5d7abc2e080599bb6dc465db54d/large.png?1363101197</AvatarUrl>
<AvatarUrl size="medium">https://assets3-my.umbc.edu/system/shared/avatars/groups/000/000/583/fc60f5d7abc2e080599bb6dc465db54d/medium.png?1363101197</AvatarUrl>
<AvatarUrl size="small">https://assets4-my.umbc.edu/system/shared/avatars/groups/000/000/583/fc60f5d7abc2e080599bb6dc465db54d/small.png?1363101197</AvatarUrl>
<AvatarUrl size="xsmall">https://assets3-my.umbc.edu/system/shared/avatars/groups/000/000/583/fc60f5d7abc2e080599bb6dc465db54d/xsmall.png?1363101197</AvatarUrl>
<AvatarUrl size="xxsmall">https://assets4-my.umbc.edu/system/shared/avatars/groups/000/000/583/fc60f5d7abc2e080599bb6dc465db54d/xxsmall.png?1363101197</AvatarUrl>
<Sponsor>Web Developer - Build Group</Sponsor>
<PawCount>0</PawCount>
<CommentCount>0</CommentCount>
<CommentsAllowed>true</CommentsAllowed>
<PostedAt>Mon, 03 Feb 2014 09:00:26 -0500</PostedAt>
<EditAt>Mon, 03 Feb 2014 09:00:21 -0500</EditAt>
</NewsItem>

<NewsItem contentIssues="true" id="40913" important="false" status="posted" url="https://my3.my.umbc.edu/posts/40913">
<Title>Creating Brackets Extensions</Title>
<Body>
<![CDATA[
    <div class="html-content">
    <a href="http://rss.buysellads.com/click.php?z=1260013&amp;k=d754f1e9ba63a736ba8ff5ece958f7dd&amp;a=36781&amp;c=152999730" rel="nofollow external" class="bo"><img src="http://rss.buysellads.com/img.php?z=1260013&amp;k=d754f1e9ba63a736ba8ff5ece958f7dd&amp;a=36781&amp;c=152999730" alt="" style="max-width: 100%; height: auto;"></a><p>A little while ago I wrote about the <a href="http://dev.tutsplus.com/tutorials/deeper-in-the-brackets-editor--net-35527" rel="nofollow external" class="bo">recent updates</a> to the Brackets editor. <a href="https://github.com/adobe/brackets" rel="nofollow external" class="bo">Brackets</a> is an open source project focused on web standards and built with web technologies. It has a narrow focus and therefore may not have a particular feature you’ve come to depend upon. Luckily, Brackets ships with a powerful extension API that lets you add any number of new features. In this article, I’m going to discuss this API and demonstrate how you can build your own extensions.</p>
    <p></p>
    <p>It is <em>crucial</em> that you remember that Brackets is in active development. This article is being written in December of 2013. It is certainly possible that the code demonstrated below is now out of date. Keep that in mind and be sure to check the <a href="https://github.com/adobe/brackets/wiki/How-to-Write-Extensions" rel="nofollow external" class="bo">wiki</a> for the latest updates to the extension API.</p>
    <hr>
    <h2>Getting Started</h2>
    <p>I’m going to assume you read my last article and are already familiar with the extension manager. This provides a simple, one click method of installing extensions. One of the best ways you can learn to write extensions is by looking at the work done by others (that’s how I learned). I’d recommend grabbing a few extensions (there’s almost 200 available now) and tearing apart their code. Don’t be afraid to break a few while you’re at it.</p>
    <p>Brackets puts all installed extensions within one main folder. To find that folder, go to the <strong>Help</strong> menu and select “<strong>Show Extensions Folder</strong>“. For my OS X install, this was located at <strong>/Users/ray/Library/Application Support/Brackets/extensions/user</strong>. If you go up from that folder, you’ll notice a disabled folder as well. Brackets will make a valiant effort to load no matter what, but if you ever find yourself in a situation where Brackets has completely crapped the bed and simply will not work, consider moving potentially bad extensions into the disabled folder. Later on in the article, I’ll discuss how you can monitor and debug extensions to help prevent such problems in the first place.</p>
    <p>Begin by going to your user folder and creating a new folder, <code>helloworld1</code>. Yes, even though it is completely lame, we’re going to build a HelloWorld extension. Don’t hate me, I like simple. Inside that folder create a new file called <code>main.js</code>. Listing one shows what the contents of this file should be. Note that in this article I’ll go through a couple of different iterations of the <code>helloworld</code> extension. Each one will be named with a progressively higher number. So our first example is from <code>helloworld1</code>, the next <code>helloworld2</code>, and so on. It would make sense for you to simply copy the code into one folder, <code>helloworld</code>, instead of copying each one by themselves. If you do, you’ll have multiple related extensions running at once and that can definitely confuse things.</p>
    <pre>Listing 1: helloworld1/main.js&#x000A;    define(function(require, exports, module) {&#x000A;    &#x000A;        function log(s) {&#x000A;                console.log("[helloworld] "+s);&#x000A;        }&#x000A;    &#x000A;        log("Hello from HelloWorld.");&#x000A;    });&#x000A;    </pre>
    <p>The first line defines our extension as a module that will be picked up by Brackets automatically on application load. The rest of the extension is a custom log message (you will see why in a second) and a call to that logger. Once you have this file saved, go back to Brackets, select the Debug menu, and hit Reload. (You can also use <strong>Command/Control+R</strong> to reload as well.)</p>
    <p>Brackets will reload and … nothing else will happen. The extension we built didn’t actually do anything that we could see, but it did log to the console. But where is that console? Brackets provides an easy way to view the console. Simply go back to the <strong>Debug</strong> menu and select <strong>Show Developer Tools</strong>. This will open a new tab in Chrome with a familiar Dev Tools UI. In the screen shot below I’ve highlighted our log. Other extensions, and Brackets itself, will also log messages to this screen. By prefixing my log messages with <code>[helloworld]</code>, I can make my own stuff a bit easier to find.</p>  <img src="http://cdn.tutsplus.com/net/uploads/2014/01/s1.jpeg" alt="Brackets console messages" width="600" height="268" style="max-width: 100%; height: auto;"><br> <p>Note that the full Chrome <code>console.api</code> works here. You can do stuff like this to format your console messages:</p>
    <pre>    &#x000A;    function log(s) {&#x000A;        console.log("%c[helloworld] "+s,"color:blue;font-size:large");&#x000A;    }&#x000A;    </pre>
    <p>Go crazy, but try to remove these messages before you share your code with the rest of the world. In case you’re curious, you can’t use dev tools in another browser, like Firefox, at this time.</p>
    <hr>
    <h2>Integration Points</h2>
    <p>Now that you know the (very) basics, let’s talk about what Brackets extensions can do to the editor:</p>
    <ul>
    <li>They can create keyboard shortcuts, allowing them to respond to custom keystrokes.</li>
    <li>They can add to the top level menu.</li>
    <li>They can add context menus (and to a specific area, like the file listing or the editor window).</li>
    <li>They can create UI items. This can be a modal dialog or even a panel. (Currently the panel is locked to the bottom of the screen).</li>
    <li>They can create a linting provider (essentially they can register themselves as a code checker for a file type).</li>
    <li>They can create their own inline editors (a major feature of Brackets).</li>
    <li>They can register as a documentation provider (for example, adding MDN support for docs).</li>
    <li>They can integrate with Quick Find and Quick Open.</li>
    <li>They can add custom code hints and syntax colors.</li>
    <li>They can read the current file open in the editor as well as modify it. (They can also see the current selected text, if any.)</li>
    </ul>
    <p>That describes how extensions can modify Brackets, but what can extensions actually do in terms of code? Keeping in mind that you’re writing extensions in pure web standards (HTML, JavaScript, and CSS), you actually have quite a bit of power. The only real limits relate to binary data. There is a File system API that gives you control over files but is limited to text data only. Luckily, you have a way out.</p>
    <p>Any Brackets extension can integrate with Node.js. If you’ve got an existing Node.js package your extension can make calls to it and do, well, whatever Node.js can do, which is essentially anything.</p>
    <p>Let’s update our extension to integrate with the editor a bit better. I’ll start by simply adding a menu item for the extension.</p>
    <pre>Listing 2: helloworld2/main.js&#x000A;    /*&#x000A;    Based - in part - on the HelloWorld sample extension on the Brackets wiki:&#x000A;    &#x000A;    <a href="https://github.com/adobe/brackets/wiki/Simple-%22Hello-World%22-extension">https://github.com/adobe/brackets/wiki/Simple-%22Hello-World%22-extension</a>&#x000A;    &#x000A;    */&#x000A;    define(function(require, exports, module) {&#x000A;    &#x000A;        var CommandManager = brackets.getModule("command/CommandManager"),&#x000A;                Menus = brackets.getModule("command/Menus"),&#x000A;                AppInit = brackets.getModule("utils/AppInit");&#x000A;        function log(s) {&#x000A;                console.log("[helloworld2] "+s);&#x000A;        }&#x000A;        function handleHelloWorld() {&#x000A;                alert("You ran me, thanks!");&#x000A;        }&#x000A;        AppInit.appReady(function () {&#x000A;    &#x000A;                log("Hello from HelloWorld2.");&#x000A;    &#x000A;                var HELLOWORLD_EXECUTE = "helloworld.execute";&#x000A;    &#x000A;                CommandManager.register("Run HelloWorld", HELLOWORLD_EXECUTE, handleHelloWorld);&#x000A;    &#x000A;                var menu = Menus.getMenu(Menus.AppMenuBar.VIEW_MENU);&#x000A;                menu.addMenuItem(HELLOWORLD_EXECUTE);&#x000A;    &#x000A;        });&#x000A;    &#x000A;    });&#x000A;    </pre>
    <p>We’ve got a few changes here so let’s tackle them one by one. You’ll notice that the extension begins with three calls to brackets.getModule. All extensions have access to a brackets object that provides an API where we can load in core functionality from the editor. In this case the extension has loaded two libraries we’ll need for the menu (CommandManager and Menus) and one which will be used to help initialize the extension (AppInit).</p>
    <p>Lets talk about AppInit. You can see that most of the extension is now loaded with a appReady callback. This callback is fired when Brackets has completed loading and is generally considered “best practice” for extensions to make use of.</p>
    <p>Registering a menu item takes a few steps. I begin by defining a “command ID”, a unique identifier for the item I’ll be adding to the UI. The typical way to do this is with the format <code>extensionname.someaction</code>. In my case, I used <code>helloworld.execute</code>. I can then register this command along with the function (<code>handleHelloWorld</code>) that should be called when the command is fired.</p>
    <p>The final step is to add this command to the menu. You can probably guess that my menu item will be added under the View menu based on this value: Menus.AppMenuBar.VIEW_MENU. How did I know that value? Simple, I saw other extensions do it. Seriously though, there is no specific list of items like this yet. Don’t forget that Brackets is open source. I can easily pop over to the GitHub repo and check it out. In this case, the file is <code>Menus.js</code>, located on <a href="https://github.com/adobe/brackets/blob/master/src/command/Menus.js" rel="nofollow external" class="bo">Github</a>. In there I can see where the various different core menus are defined:</p>
    <pre>/**&#x000A;      * Brackets Application Menu Constants&#x000A;      * @enum {string}&#x000A;    */&#x000A;    var AppMenuBar = {&#x000A;         FILE_MENU       : "file-menu",&#x000A;         EDIT_MENU       : "edit-menu",&#x000A;         VIEW_MENU       : "view-menu",&#x000A;         NAVIGATE_MENU   : "navigate-menu",&#x000A;         HELP_MENU       : "help-menu"&#x000A;    };      &#x000A;    </pre>
    <p>As a general rule of thumb, it makes sense to have at least a cursory understanding of what’s available in Brackets itself. Your extensions will, from time to time, make use of multiple different features so it’s definitely in your best interest to at least know the lay of the land.</p>
    <p>After reloading Brackets, you’ll now see the menu item in the <strong>View</strong> menu. Exactly where it is may be a bit random as you may have other extensions installed.</p>  <img src="http://cdn.tutsplus.com/net/uploads/2014/01/s2.png" alt="View menu updated" width="600" height="447" style="max-width: 100%; height: auto;"><br> <p>You can actually be a bit more specific about your position. Again, this is where the source code will help you. The same file I linked to above also contains the addMenuItem definition.</p>
    <hr>
    <h2>Put Some Lipstick on That Pig</h2>
    <p>Now that you’ve seen a simple example of how an extension can integrate into Brackets, let’s look at how we update the UI. In the previous version of our code, an alert was used to send a message. While this works, it isn’t very pretty. Your code can access the Brackets editor just like any other DOM modification code. While you <em>can</em> do anything you want, there are a few standard ways extensions update the UI in Brackets. (As a warning, in general you do not want to touch the DOM of the main editor UI. You can, but with future updates, your code may break. Also, users may not be happy if your extension changes something core to Brackets.)</p>
    <p>The first method we’ll look at uses modal dialogs. Brackets already uses this and has an API available for extensions to call. As a simple example, let’s just update the HelloWorld extension to use a modal instead.</p>
    <pre>Listing 3: helloworld3/main.js&#x000A;    /*&#x000A;    Based - in part - on the HelloWorld sample extension on the Brackets wiki:&#x000A;    &#x000A;    <a href="https://github.com/adobe/brackets/wiki/Simple-%22Hello-World%22-extension">https://github.com/adobe/brackets/wiki/Simple-%22Hello-World%22-extension</a>&#x000A;    &#x000A;    */&#x000A;    define(function(require, exports, module) {&#x000A;    &#x000A;        var CommandManager = brackets.getModule("command/CommandManager"),&#x000A;            Menus = brackets.getModule("command/Menus"),&#x000A;            Dialogs = brackets.getModule("widgets/Dialogs"),&#x000A;            DefaultDialogs = brackets.getModule("widgets/DefaultDialogs"),&#x000A;            AppInit = brackets.getModule("utils/AppInit");&#x000A;    &#x000A;        function log(s) {&#x000A;                console.log("[helloworld3] "+s);&#x000A;        }&#x000A;    &#x000A;        function handleHelloWorld() {&#x000A;            Dialogs.showModalDialog(DefaultDialogs.DIALOG_ID_INFO, "Hello World", "Same Important Message");&#x000A;        }&#x000A;    &#x000A;        AppInit.appReady(function () {&#x000A;    &#x000A;            log("Hello from HelloWorld3.");&#x000A;    &#x000A;            var HELLOWORLD_EXECUTE = "helloworld.execute";&#x000A;    &#x000A;            CommandManager.register("Run HelloWorld", HELLOWORLD_EXECUTE, handleHelloWorld);&#x000A;    &#x000A;            var menu = Menus.getMenu(Menus.AppMenuBar.VIEW_MENU);&#x000A;            menu.addMenuItem(HELLOWORLD_EXECUTE);&#x000A;    &#x000A;        });&#x000A;    &#x000A;    });&#x000A;    </pre>
    <p>Note the addition of two additional Brackets modules: <code>Dialogs</code> and <code>DefaultDialogs</code>. The next change is in <code>handleHelloWorld</code>. One of the methods in the Dialog library is the ability to show a dialog (no surprise there, I suppose). The method wants a class, a title, and a body, and that’s it. There’s more you can do with dialogs, but for now, this demonstrates the feature. Now when we run the command, we get a much prettier UI. (Along with default buttons and behaviours to handle closing the dialog.)</p>  <img src="http://cdn.tutsplus.com/net/uploads/2014/01/s3.png" alt="Dialog example" width="600" height="397" style="max-width: 100%; height: auto;"><br> <p>That’s one example, now lets look at another: creating a bottom panel. As with dialogs, we’ve got support from Brackets to make it easier. Let’s look at an example and then I’ll explain the changes.</p>
    <pre>Listing 4: helloworld4/main.js&#x000A;    /*&#x000A;    Based - in part - on the HelloWorld sample extension on the Brackets wiki:&#x000A;    &#x000A;    <a href="https://github.com/adobe/brackets/wiki/Simple-%22Hello-World%22-extension">https://github.com/adobe/brackets/wiki/Simple-%22Hello-World%22-extension</a>&#x000A;    &#x000A;    */&#x000A;    define(function(require, exports, module) {&#x000A;    &#x000A;        var CommandManager = brackets.getModule("command/CommandManager"),&#x000A;        Menus = brackets.getModule("command/Menus"),&#x000A;        PanelManager = brackets.getModule("view/PanelManager"),&#x000A;        AppInit = brackets.getModule("utils/AppInit");&#x000A;    &#x000A;        var HELLOWORLD_EXECUTE = "helloworld.execute";&#x000A;        var panel;&#x000A;    &#x000A;        function log(s) {&#x000A;                console.log("[helloworld4] "+s);&#x000A;        }&#x000A;    &#x000A;        function handleHelloWorld() {&#x000A;            if(panel.isVisible()) {&#x000A;                panel.hide();&#x000A;                CommandManager.get(HELLOWORLD_EXECUTE).setChecked(false);&#x000A;            } else {&#x000A;                panel.show();&#x000A;                CommandManager.get(HELLOWORLD_EXECUTE).setChecked(true);&#x000A;            }&#x000A;        }&#x000A;    &#x000A;        AppInit.appReady(function () {&#x000A;    &#x000A;                log("Hello from HelloWorld4.");&#x000A;    &#x000A;                CommandManager.register("Run HelloWorld", HELLOWORLD_EXECUTE, handleHelloWorld);&#x000A;    &#x000A;                var menu = Menus.getMenu(Menus.AppMenuBar.VIEW_MENU);&#x000A;                menu.addMenuItem(HELLOWORLD_EXECUTE);&#x000A;    &#x000A;                panel = PanelManager.createBottomPanel(HELLOWORLD_EXECUTE, $("&lt;div class='bottom-panel'&gt;HTML for my panel&lt;/div&gt;"),200);&#x000A;    &#x000A;        });&#x000A;    &#x000A;    });&#x000A;    </pre>
    <p>Let’s focus on the changes. First, I dropped the Dialog modules as I’m no longer using them. Instead, we load up the PanelManager. Down in the appReady block I’ve defined a new panel using the PanelManager API method createBottomPanel. Like the menu command this takes in a unique ID so I just reuse <code>HELLOWORLD_EXECUTE</code>. The second argument is a jQuery-wrapped block of HTML (and in case you’re wondering, yes we can do this nicer), and finally, a minimum size. This sets up the panel but doesn’t actually execute it.</p>
    <p>In the event handler, we have tied to the menu, we can ask the panel if it is visible and then either hide or show it. That part should be pretty trivial. For fun I’ve added in a bit more complexity. Notice that <code>CommandManager</code> lets us get a menu item and set a checked property. This may be unnecessary as the user can see the panel easily enough themselves, but adding the check just makes things a little bit more obvious. In the screen shot below you can see the panel in its visible state.</p>  <img src="http://cdn.tutsplus.com/net/uploads/2014/01/s4.png" alt="Panel example" width="600" height="493" style="max-width: 100%; height: auto;"><br> <p>Right away you may be wondering about the panel HTML. Is there a better way to provide the HTML? Anyway to style it? Yep, lets look at a more advanced version.</p>
    <pre>Listing 5: helloworld5/main.js&#x000A;    /*&#x000A;    Based - in part - on the HelloWorld sample extension on the Brackets wiki:&#x000A;    &#x000A;    <a href="https://github.com/adobe/brackets/wiki/Simple-%22Hello-World%22-extension">https://github.com/adobe/brackets/wiki/Simple-%22Hello-World%22-extension</a>&#x000A;    &#x000A;    */&#x000A;    define(function(require, exports, module) {&#x000A;        var CommandManager = brackets.getModule("command/CommandManager"),&#x000A;        Menus = brackets.getModule("command/Menus"),&#x000A;        PanelManager = brackets.getModule("view/PanelManager"),&#x000A;        ExtensionUtils          = brackets.getModule("utils/ExtensionUtils"),        &#x000A;        AppInit = brackets.getModule("utils/AppInit");&#x000A;    &#x000A;        var HELLOWORLD_EXECUTE = "helloworld.execute";&#x000A;        var panel;&#x000A;        var panelHtml     = require("text!panel.html");&#x000A;    &#x000A;        function log(s) {&#x000A;                console.log("[helloworld5] "+s);&#x000A;        }&#x000A;    &#x000A;        function handleHelloWorld() {&#x000A;            if(panel.isVisible()) {&#x000A;                panel.hide();&#x000A;                CommandManager.get(HELLOWORLD_EXECUTE).setChecked(false);&#x000A;            } else {&#x000A;                panel.show();&#x000A;                CommandManager.get(HELLOWORLD_EXECUTE).setChecked(true);&#x000A;            }&#x000A;        }&#x000A;    &#x000A;        AppInit.appReady(function () {&#x000A;    &#x000A;            log("Hello from HelloWorld5.");&#x000A;            ExtensionUtils.loadStyleSheet(module, "helloworld.css");&#x000A;            CommandManager.register("Run HelloWorld", HELLOWORLD_EXECUTE, handleHelloWorld);&#x000A;    &#x000A;            var menu = Menus.getMenu(Menus.AppMenuBar.VIEW_MENU);&#x000A;            menu.addMenuItem(HELLOWORLD_EXECUTE);&#x000A;    &#x000A;            panel = PanelManager.createBottomPanel(HELLOWORLD_EXECUTE, $(panelHtml),200);&#x000A;    &#x000A;        });&#x000A;    &#x000A;    });&#x000A;    </pre>
    <p>As before, I’m going to focus on the changes. First note that I’ve included a variable called <code>panelHtml</code> that is loaded via require. This lets me define my HTML outside of my JavaScript code. (You can also use templating engines. Brackets ships with Mustache.) The HTML behind the panel is rather simple.</p>
    <pre>Listing 6: helloworld5/panel.html&#x000A;    &lt;div class="bottom-panel helloworld-panel" id="helloworldPanel"&gt;&#x000A;    &lt;h1&gt;My Panel&lt;/h1&gt;&#x000A;    &#x000A;    &lt;p&gt;&#x000A;    My panel brings all the boys to the yard,&lt;br/&gt;&#x000A;    And they're like&lt;br/&gt;&#x000A;    It's better than yours,&lt;br/&gt;&#x000A;    Damn right it's better than yours,&lt;br/&gt;&#x000A;    I can teach you,&lt;br/&gt;&#x000A;    But I have to charge&#x000A;    &lt;/p&gt;&#x000A;    &lt;/div&gt;&#x000A;    </pre>
    <p>Returning to <code>main.js</code>, I’ve demonstrated another feature, loadStyleSheet. This lets you load an extension specific style sheet. I created a file, <code>helloworld.css</code>, with some simple (but tasteful) CSS styles.</p>
    <pre>Listing 7: helloworld5/helloworld.css&#x000A;    .helloworld-panel h1 {&#x000A;            color: red;&#x000A;    }&#x000A;    &#x000A;    .helloworld-panel p {&#x000A;            color: blue;&#x000A;            font-weight: bold;&#x000A;    }&#x000A;    </pre>
    <p>Note that I prefixed my styles with a unique name. This helps ensure my classes don’t conflict with anything built into Brackets. With these simple changes my panel now looks much better, and you can see why I’m known world wide for my superior design skills.</p>  <img src="http://cdn.tutsplus.com/net/uploads/2014/01/a5.png" alt="Epic CSS" width="600" height="396" style="max-width: 100%; height: auto;"><br> <hr>
    <h2>Packaging and Sharing Your Kick Butt Extension</h2>
    <p>Of course, just creating the coolest Brackets extension isn’t quite enough. You probably (hopefully!) want to share it with others. One option is to just zip up the directory and put it on your website. Folks can download the zip, extract it, and copy it to their Brackets extensions folder.</p>
    <p>But that’s not cool. You want to be cool, right? In order to share your extension and make it available via the Brackets Extension manager, you simply need to add a <code>package.json</code> file to your extension. If you’ve ever used Node.js, then this will seem familiar. Here is a sample one for our extension.</p>
    <pre>Listing 8: helloworld6/package.json&#x000A;    {&#x000A;        "name": "camden.helloworld",&#x000A;        "title": "HelloWorld",&#x000A;        "description": "Adds HelloWorld support to Brackets.",&#x000A;        "homepage": "<a href="https://github.com/cfjedimaster/something">https://github.com/cfjedimaster/something</a> real here",&#x000A;        "version": "1.0.0",&#x000A;        "author": "Raymond Camden &lt;<a href="mailto:raymondcamden@gmail.com">raymondcamden@gmail.com</a>&gt; (<a href="http://www.raymondcamden.com">http://www.raymondcamden.com</a>)",&#x000A;        "license": "MIT",&#x000A;        "engines": {&#x000A;            "brackets": "&lt;=0.34.0"&#x000A;        }&#x000A;    }&#x000A;    </pre>
    <p>Most of this is self-explanatory, but the real crucial portion is the engines block. Brackets updates itself pretty rapidly. If Brackets added a particular feature at some point that your extension relies on, you can add a simple conditional here to ensure folks don’t try to install your extension on an incompatible version. (You can find a full listing of the possible settings on <a href="https://github.com/adobe/brackets/wiki/Extension-package-format#packagejson-format" rel="nofollow external" class="bo">the Wiki</a>.)</p>
    <p>Once you’ve done this, the next part is to upload it to the <a href="https://brackets-registry.aboutweb.com/" rel="nofollow external" class="bo">Brackets Registry</a>. You will need to log in via your GitHub account, but once you’ve done that, you can then simply upload your zip. Your extension will then be available to anyone using Brackets. Even better, if you update your extension, the Extension Manager will actually be able to flag this to the user so they know an update is available.</p>
    <hr>
    <h2>What Else?</h2>
    <p>Hopefully, you’ve seen how easy it is to extend Brackets. There’s more we didn’t cover, like the <a href="http://blog.brackets.io/2013/10/07/new-linting-api/" rel="nofollow external" class="bo">Linting API</a> and <a href="https://github.com/adobe/brackets/wiki/Brackets-Node-Process:-Overview-for-Developers#usage-example" rel="nofollow external" class="bo">NodeJS integration</a>, but this article should be more than enough to get you started. As a reminder, do not forget there is a large collection of extensions available for you to start playing with right now. Good luck!</p>
    </div>
]]>
</Body>
<Summary>A little while ago I wrote about the recent updates to the Brackets editor. Brackets is an open source project focused on web standards and built with web technologies. It has a narrow focus and...</Summary>
<Website>http://feedproxy.google.com/~r/nettuts/~3/OnsSgMdfwkY/</Website>
<TrackingUrl>https://my3.my.umbc.edu/api/v0/pixel/news/40913/guest@my.umbc.edu/061c9d55f8ae6dfba682b43935354f37/api/pixel</TrackingUrl>
<Tag>brackets</Tag>
<Tag>css</Tag>
<Tag>development</Tag>
<Tag>extensions</Tag>
<Tag>html</Tag>
<Tag>javascript</Tag>
<Tag>javascript-and-ajax</Tag>
<Tag>mysql</Tag>
<Tag>php</Tag>
<Tag>sql</Tag>
<Tag>tutorials</Tag>
<Tag>wed</Tag>
<Group token="retired-583">Web Developer - Build Group</Group>
<GroupUrl>https://my3.my.umbc.edu/groups/retired-583</GroupUrl>
<AvatarUrl>https://assets3-my.umbc.edu/system/shared/avatars/groups/000/000/583/fc60f5d7abc2e080599bb6dc465db54d/xsmall.png?1363101197</AvatarUrl>
<AvatarUrl size="original">https://assets4-my.umbc.edu/system/shared/avatars/groups/000/000/583/fc60f5d7abc2e080599bb6dc465db54d/original.jpg?1363101197</AvatarUrl>
<AvatarUrl size="xxlarge">https://assets4-my.umbc.edu/system/shared/avatars/groups/000/000/583/fc60f5d7abc2e080599bb6dc465db54d/xxlarge.png?1363101197</AvatarUrl>
<AvatarUrl size="xlarge">https://assets4-my.umbc.edu/system/shared/avatars/groups/000/000/583/fc60f5d7abc2e080599bb6dc465db54d/xlarge.png?1363101197</AvatarUrl>
<AvatarUrl size="large">https://assets3-my.umbc.edu/system/shared/avatars/groups/000/000/583/fc60f5d7abc2e080599bb6dc465db54d/large.png?1363101197</AvatarUrl>
<AvatarUrl size="medium">https://assets3-my.umbc.edu/system/shared/avatars/groups/000/000/583/fc60f5d7abc2e080599bb6dc465db54d/medium.png?1363101197</AvatarUrl>
<AvatarUrl size="small">https://assets4-my.umbc.edu/system/shared/avatars/groups/000/000/583/fc60f5d7abc2e080599bb6dc465db54d/small.png?1363101197</AvatarUrl>
<AvatarUrl size="xsmall">https://assets3-my.umbc.edu/system/shared/avatars/groups/000/000/583/fc60f5d7abc2e080599bb6dc465db54d/xsmall.png?1363101197</AvatarUrl>
<AvatarUrl size="xxsmall">https://assets4-my.umbc.edu/system/shared/avatars/groups/000/000/583/fc60f5d7abc2e080599bb6dc465db54d/xxsmall.png?1363101197</AvatarUrl>
<Sponsor>Web Developer - Build Group</Sponsor>
<PawCount>0</PawCount>
<CommentCount>0</CommentCount>
<CommentsAllowed>true</CommentsAllowed>
<PostedAt>Mon, 03 Feb 2014 09:00:21 -0500</PostedAt>
<EditAt>Mon, 03 Feb 2014 09:00:21 -0500</EditAt>
</NewsItem>

<NewsItem contentIssues="false" id="40911" important="false" status="posted" url="https://my3.my.umbc.edu/posts/40911">
<Title>Late New News</Title>
<Tagline>Continuing Coverage of Not Much</Tagline>
<Body>
<![CDATA[
    <div class="html-content">Greetings everyone!<br><br>To all of you who were at our meeting last friday, we essentially covered the points laid out in the last news update.<br><br>In terms of updates, we're looking for people to begin brainstorming new event ideas for this month. We will have a plan for our monthly event by wednesday. I super promise.<br><br>Additionally, I will be working on updating our youtube channel today with at least one silly furry video, so go check that out.<br><br>Alright, looks like we're done here. Akabra out.<br>
    </div>
]]>
</Body>
<Summary>Greetings everyone!  To all of you who were at our meeting last friday, we essentially covered the points laid out in the last news update.  In terms of updates, we're looking for people to begin...</Summary>
<TrackingUrl>https://my3.my.umbc.edu/api/v0/pixel/news/40911/guest@my.umbc.edu/a65e36e05c9090e18d29cefc5e49b957/api/pixel</TrackingUrl>
<Group token="retired-489">Federation of Furry Fans of the Furry Fandom</Group>
<GroupUrl>https://my3.my.umbc.edu/groups/retired-489</GroupUrl>
<AvatarUrl>https://assets4-my.umbc.edu/images/avatars/group/7/xsmall.png?1789738931</AvatarUrl>
<AvatarUrl size="original">https://assets1-my.umbc.edu/images/avatars/group/7/original.png?1789738931</AvatarUrl>
<AvatarUrl size="xxlarge">https://assets1-my.umbc.edu/images/avatars/group/7/xxlarge.png?1789738931</AvatarUrl>
<AvatarUrl size="xlarge">https://assets1-my.umbc.edu/images/avatars/group/7/xlarge.png?1789738931</AvatarUrl>
<AvatarUrl size="large">https://assets1-my.umbc.edu/images/avatars/group/7/large.png?1789738931</AvatarUrl>
<AvatarUrl size="medium">https://assets2-my.umbc.edu/images/avatars/group/7/medium.png?1789738931</AvatarUrl>
<AvatarUrl size="small">https://assets1-my.umbc.edu/images/avatars/group/7/small.png?1789738931</AvatarUrl>
<AvatarUrl size="xsmall">https://assets4-my.umbc.edu/images/avatars/group/7/xsmall.png?1789738931</AvatarUrl>
<AvatarUrl size="xxsmall">https://assets2-my.umbc.edu/images/avatars/group/7/xxsmall.png?1789738931</AvatarUrl>
<Sponsor>Federation of Furry Fans of the Furry Fandom</Sponsor>
<PawCount>0</PawCount>
<CommentCount>1</CommentCount>
<CommentsAllowed>true</CommentsAllowed>
<PostedAt>Mon, 03 Feb 2014 08:31:58 -0500</PostedAt>
</NewsItem>

<NewsItem contentIssues="true" id="40908" important="false" status="posted" url="https://my3.my.umbc.edu/posts/40908">
<Title>One Solution To Responsive Images</Title>
<Body>
<![CDATA[
    <div class="html-content">
    <table width="650">
    <tbody>
    <tr>
    <td>
    <div>
    <img src="http://statisches.auslieferung.commindo-media-ressourcen.de/advertisement.gif" alt="" style="max-width: 100%; height: auto;"><br><a href="http://auslieferung.commindo-media-ressourcen.de/random.php?mode=target&amp;collection=smashing-rss&amp;position=1" rel="nofollow external" class="bo"><img src="http://auslieferung.commindo-media-ressourcen.de/random.php?mode=image&amp;collection=smashing-rss&amp;position=1" alt="" style="max-width: 100%; height: auto;"></a> <a href="http://auslieferung.commindo-media-ressourcen.de/random.php?mode=target&amp;collection=smashing-rss&amp;position=2" rel="nofollow external" class="bo"><img src="http://auslieferung.commindo-media-ressourcen.de/random.php?mode=image&amp;collection=smashing-rss&amp;position=2" alt="" style="max-width: 100%; height: auto;"></a> <a href="http://auslieferung.commindo-media-ressourcen.de/random.php?mode=target&amp;collection=smashing-rss&amp;position=3" rel="nofollow external" class="bo"><img src="http://auslieferung.commindo-media-ressourcen.de/random.php?mode=image&amp;collection=smashing-rss&amp;position=3" alt="" style="max-width: 100%; height: auto;"></a>
    </div>
    </td>
    </tr>
    </tbody>
    </table>
    <p>Responsive images have been, and are, one of the hardest problems in responsive Web design right now. <strong>Until browser vendors give us a native solution</strong>, we have to think on the fly and come up with our own solutions. “Retina” images are especially a challenge because if you have sized your layout with ems or percentages (as you should!), then you cannot be sure of the exact pixel dimensions of each image being displayed.</p>
    <p>In this article, we’ll look at one solution to the problem that we implemented on our portfolio website at <a href="http://etchapps.com" title="Etch Apps" rel="nofollow external" class="bo">Etch</a>, where you can see an early working version in the wild.</p>
    <h3>Requirements</h3>
    <p>We used a <a href="http://adactio.com/journal/4523/" title="Jeremy Keith blog post on content first" rel="nofollow external" class="bo">content-first</a> approach on Etch. We knew we wanted to use a lot of images to quickly convey the atmosphere of the company. These would be accompanied by small snippets, or “soundbites,” of text.</p>
    <p>The next decision was on image sizes and aspect ratios. To get maximum control over the design, we knew we needed maximum control over the images. We decided to use Instagram as the base for our imagery for the following reasons:</p>
    <ul>
    <li>The aspect ratio is fixed.</li>
    <li>Most employees here already use it.</li>
    <li>Those lovely filters.</li>
    </ul>
    <p>Instagram allows for a maximum image size of 600 pixels, so we now had our first set of content constraints to work with: images with a 1:1 aspect ratio, and a maximum image size of 600 × 600. Having constraints on the content side made the design process easier because they limited our options, thus forcing decisions.</p>
    <p>When the content was completed, we began looking at the design. Again, to keep maximum control, we decided on an adaptive design style with fixed column sizes. We used grid block elements that match our maximum image size. Each grid block would either be 600 × 600 or 300 × 300, which also conveniently fit our rough plan of a minimum width of 320 pixels for the viewport on the website.
    </p>
    <p>During the rest of the design process, we noticed that we needed two other image sizes: thumbnails at 100 × 100, and hero images that stretch the full width of the content (300, 600, 900, 1200, 1600, 1800). All images would also need to be “Retina” ready — or, to put it another way, compatible with displays with high pixel densities. This gave us the final set of requirements for a responsive images solution for the website:</p>
    <ul>
    <li>Potential image widths (in pixels) of 100, 300, 600, 900, 1200, 1600, 1800</li>
    <li>Retina ready</li>
    <li>Must be crisp with minimal resizing (some people notice a drop in quality with even downsized images)</li>
    </ul>
    <p>Having to resize that many images manually, even using a Photoshop script, seemed like too much work. Anything like that should be automated, so that you can focus on fun and interesting coding instead. Automation also removes the chance for human error, like forgetting to do it. The ideal solution would be for us to add an image file once and forget about it.</p>
    <h3>Common Solutions</h3>
    <p>Before going over our solution, let’s look at some common solutions currently being used. To keep up with currently popular methods and the work that the Web community is doing to find a solution to responsive images, head over to the <a href="http://www.w3.org/community/respimg/wiki/Main_Page" title="W3C Responsive Images" rel="nofollow external" class="bo">W3C Responsive Images Community Group</a>.</p>
    <h4>Picture Element</h4>
    <p>First up, the <a href="http://picture.responsiveimages.org/" title="Further info on the picture element" rel="nofollow external" class="bo">picture</a> element. While this doesn’t currently have native support and browser vendors are still deciding on <code>picture</code> versus <code>srcset</code> versus whatever else is up for discussion, we can use it with a polyfill.</p>
    <pre><code>&#x000A;    &lt;picture alt="description"&gt;&#x000A;      &lt;source src="small.jpg"&gt;&#x000A;      &lt;source src="medium.jpg" media="(min-width: 40em)"&gt;&#x000A;      &lt;source src="large.jpg" media="(min-width: 80em)"&gt;&#x000A;    &lt;/picture&gt;&#x000A;    </code></pre>
    <p>The <code>picture</code> element is great if you want to serve images with a different shape, focal point or other feature beyond just resizing. However, you’ll have to presize all of the different images to be ready to go straight in the HTML. This solution also couples HTML with media queries, and we know that coupling CSS to HTML is bad for maintenance. This solution also doesn’t cover high-definition displays</p>
    <p>For this project, the <code>picture</code> element required too much configuration and manual creation and storage of the different image sizes and their file paths.</p>
    <h4>srcset</h4>
    <p>Another popular solution, <a href="http://www.w3.org/html/wg/drafts/srcset/w3c-srcset/" title="W3C srcset docs" rel="nofollow external" class="bo">srcset</a>, has recently been made available natively in some WebKit-based browsers. At the time of creating our plugin, this wasn’t available, and it looks like we’ll be waiting a while longer until cross-browser compatibility is good enough to use it without a JavaScript fallback. At the time of writing, <code>srcset</code> is usable only in the Chrome and Safari nightly builds.</p>
    <pre><code>&#x000A;    &lt;img src="fallback.jpg" srcset="small.jpg 640w 1x, small-hd.jpg 640w 2x, large.jpg 1x, large-hd.jpg 2x" alt="…"&gt;&#x000A;    </code></pre>
    <p>The snippet above shows <code>srcset</code> in use. Again, we see what essentially amounts to media queries embedded in HTML, which really bugs me. We’d also need to create different image sizes before runtime, which means either setting up a script or manually doing it, a tiresome job.</p>
    <h4>Server-Side Sniffing</h4>
    <p>If you’d rather not use JavaScript to decide which image to serve, you could try sniffing out the user agent server-side and automatically send an appropriately sized image. As a blanket rule, we almost always say don’t rely on server-side sniffing. It’s very unreliable, and many browsers contain inaccurate UA strings. On top of that, the sheer number of new devices and screen sizes coming out every month will lead you to maintenance hell.</p>
    <h4>Other Solutions in the Wild</h4>
    <p>We chose to make our own plugin because including layout code in the HTML seemed undesirable and having to create different image sizes beforehand was not enticing.</p>
    <p>If you’d like to explore other common solutions to decide which is best for your project, several great articles and examples are available on the Web, including one on this very website.</p>
    <ul>
    <li>“<a href="http://mobile.smashingmagazine.com/2013/07/08/choosing-a-responsive-image-solution/" rel="nofollow external" class="bo">Choosing a Responsive Image Solution</a>,” Sherri Alexander, Smashing Magazine<br>
    Alexander looks at the high-level requirements for responsive images, and then dissects the variety of solutions currently available in the wild.</li>
    <li>“<a href="http://css-tricks.com/which-responsive-images-solution-should-you-use/" rel="nofollow external" class="bo">Which Responsive Image Solution Should You Use</a>,” Chris Coyier, CSS-Tricks<br>
    Coyer takes us through imaging requirements while suggesting appropriate solutions.</li>
    <li>
    <a href="http://adaptive-images.com/" rel="nofollow external" class="bo">Adaptive Images</a><br>
    A solution very similar to Etch’s in its implementation. It uses a PHP script to size and serve the appropriate images. Unfortunately, this wasn’t available when we were coding the website.</li>
    <li>
    <a href="http://www.sitepoint.com/responsive-images-using-picturefill-php/" rel="nofollow external" class="bo">Picturefill</a><br>
    This is a JavaScript replacement for markup in the style of the <code>picture</code> element.</li>
    <li>“<a href="http://blog.keithclark.co.uk/responsive-images-using-cookies/" rel="nofollow external" class="bo">Responsive Images Using Cookies</a>,” Keith Clark<br>
    Clark uses a cookie to store the screen’s size, and then images are requested via a PHP script. Again, it’s similar to our solution but wasn’t available at the time.</li>
    </ul>
    <p>Onto our solution.</p>
    <h3>Our Solution</h3>
    <p>With both <code>picture</code> and <code>srcset</code> HTML syntaxes seeming like too much effort in the wrong places, we looked for a simpler solution. We wanted to be able to add a single image path and let the CSS, JavaScript and PHP deal with serving the correct image — instead of the HTML, which should simply have the correct information in place.</p>
    <p>At the time of developing the website, no obvious solution matched our requirements. Most centered on emulating <code>picture</code> or <code>srcset</code>, which we had already determined weren’t right for our needs.</p>
    <p>The Etch website is very image-heavy, which would make manually resizing each image a lengthy process and prone to human error. Even running an automated Photoshop script was deemed to require too much maintenance.</p>
    <p>Our solution was to find the display width of the image with JavaScript at page-loading time, and then pass the <code>src</code> and <code>width</code> to a PHP script, which would resize and cache the images on the fly before inserting them back into the DOM.</p>
    <p>We’ll look at an abstracted example of the code, written in HTML, JavaScript, PHP and LESS. You can find a <a href="http://gavyn-mckenzie.co.uk/examples/resize/" title="Demo" rel="nofollow external" class="bo">working demo</a> on my website. If you’d like to grab the files for the demo, they can be found <a href="https://github.com/gavmck/resize" rel="nofollow external" class="bo">on GitHub</a>.</p>
    <h3>Markup</h3>
    <p>The markup for the demo can be found in the <code>index.html</code> <a href="https://github.com/gavmck/resize/blob/master/index.html" rel="nofollow external" class="bo">file on GitHub</a>.</p>
    <p>We wrap the highest-resolution version of an image in <code>noscript</code> tags, for browsers with JavaScript turned off. The reason is that, if we think of performance as a feature and JavaScript as an enhancement, then non-JavaScript users would still receive the content, just not an optimized experience of that content. These <code>noscript</code> elements are then wrapped in a <code>div</code> element, with the image’s <code>src</code> and <code>alt</code> properties as data attributes. This provides the information that the JavaScript needs to send to the server.</p>
    <pre><code>&#x000A;    &lt;div data-src="img/screen.JPG" data-alt="crispy" class="img-wrap js-crispy"&gt;&#x000A;        &lt;noscript&gt;&lt;img src="img/screen.JPG" alt="Crispy"&gt;&lt;/noscript&gt;&#x000A;    &lt;/div&gt;&#x000A;    </code></pre>
    <p>The background of the image wrapper is set as a loading GIF, to show that the images are still loading and not just broken.</p>
    <p>An alternative (which we used in one of our side projects, <a href="http://phosho.co" title="PhoSho Instagram Galleries" rel="nofollow external" class="bo">PhoSho</a>) is to use the lowest-resolution size of the image that you will be displaying (if known), instead of the loading GIF. This takes slightly more bandwidth because more than one image is being loaded, but it has an appearance similar to that of progressive JPEGs as the page is loading. As always, see what your requirements dictate.</p>
    <p><a href="http://media.smashingmagazine.com/wp-content/uploads/2014/02/Dinner-2013-large-opt.jpg" rel="nofollow external" class="bo"><img src="http://media.smashingmagazine.com/wp-content/uploads/2014/01/Dinner-2013-Showcase-by-gavmcksnow-639x1024.jpg" alt="Dinner 2013 - Showcase by gavmcksnow" width="500" height="801" style="max-width: 100%; height: auto;"></a><br><em>(<a href="http://media.smashingmagazine.com/wp-content/uploads/2014/02/Dinner-2013-large-opt.jpg" rel="nofollow external" class="bo">Large preview</a>)</em></p>
    <h3>JavaScript</h3>
    <p>The JavaScript communicates for us between the HTML and the server. It fetches an array of images from the DOM, with their corresponding widths, and retrieves the appropriate cached image file from the server.</p>
    <p>Our original plugin sent one request to the server per image, but this caused a lot of extra requests. By bundling our images together as an array, we cut down the requests and kept the server happy.</p>
    <p>You can find the <a href="https://github.com/gavmck/resize/blob/master/js/resize.js" rel="nofollow external" class="bo">JavaScript plugin</a> in <code>/js/resize.js</code> in the GitHub repository.</p>
    <p>First, we set an array of breakpoints in the plugin that are the same as the breakpoints in the CSS where the image sizes change. We used em values for the breakpoints because they are based on the display font size. This is a good practice because visually impaired users might change their display’s default font size. This also makes it easier to match our CSS breakpoints with the JavaScript ones. If you prefer, the plugin works just fine with pixel-based breakpoints.</p>
    <pre><code>&#x000A;    breakpoints: [&#x000A;        "32em"&#x000A;        "48em"&#x000A;        "62em"&#x000A;        "76em"&#x000A;    ]&#x000A;    </code></pre>
    <p>As we pass each of these breakpoints, we need to check the images to make sure they are the correct size. At page-loading time, we first set the current breakpoint being displayed to the user using the JavaScript <code>matchMedia</code> function. If you need to support old browsers (Internet Explorer 7, 8 and 9), you might require the <a href="https://github.com/paulirish/matchMedia.js/" title="matchMedia polyfill on github" rel="nofollow external" class="bo">matchMedia polyfill</a> by Paul Irish.</p>
    <pre><code>&#x000A;    getCurrentBreakpoint: function() {&#x000A;          var bp, breakpoint, _fn, _i, _len, _ref,&#x000A;            _this = this;&#x000A;    &#x000A;          bp = this.breakpoints[0];&#x000A;          &#x000A;          _ref = this.breakpoints;&#x000A;          &#x000A;          _fn = function(breakpoint) {&#x000A;            // Check if the breakpoint passes&#x000A;            if (window.matchMedia &amp;&amp; window.matchMedia("all and (min-width: " + breakpoint + ")").matches) {&#x000A;              return bp = breakpoint;&#x000A;            }&#x000A;          };&#x000A;          &#x000A;          for (_i = 0, _len = _ref.length; _i &lt; _len; _i++) {&#x000A;            breakpoint = _ref[_i];&#x000A;            _fn(breakpoint);&#x000A;          }&#x000A;          &#x000A;          return bp;&#x000A;        }&#x000A;    </code></pre>
    <p>After setting the current breakpoint, we gather the images to be resized from the DOM by looping through them and adding them to the plugin’s <code>images</code> array.</p>
    <pre><code>&#x000A;    gather: function() {&#x000A;          var el, els, _i, _len;&#x000A;    &#x000A;          els = $(this.els);&#x000A;          &#x000A;          this.images = [];&#x000A;          &#x000A;          for (_i = 0, _len = els.length; _i &lt; _len; _i++) {&#x000A;            el = els[_i];&#x000A;            this.add(el);&#x000A;          }&#x000A;          &#x000A;          this.grabFromServer();&#x000A;        }&#x000A;    </code></pre>
    <p>The PHP script on the server needs the image’s <code>src</code> and current width in order to resize it correctly, so we created some serialized <code>POST</code> data to send to the server. We use jQuery’s <code>param</code> method to quickly convert the image into a usable query string.</p>
    <pre><code>&#x000A;    buildQuery: function() {&#x000A;          var image = { image: this.images }&#x000A;          return $.param(image);&#x000A;        }&#x000A;    </code></pre>
    <p>The images are then sent via an AJAX request to the server to be resized. Note the single request, to minimize server load.</p>
    <pre><code>&#x000A;    grabFromServer: function() {&#x000A;          var data,&#x000A;            _this = this;&#x000A;    &#x000A;          data = this.buildQuery();&#x000A;          &#x000A;          $.get("resize.php", data, function(data) {&#x000A;              var image, _i, _len;&#x000A;              for (_i = 0, _len = data.length; _i &lt; _len; _i++) {&#x000A;                image = data[_i];&#x000A;                _this.loadImage(image);&#x000A;              }&#x000A;            }&#x000A;          );&#x000A;        }&#x000A;    </code></pre>
    <p>Once we have retrieved the images from the server, we can add them to the DOM or replace the image already in place if it has changed. If it’s the same image, then nothing happens and the image won’t need to be redownloaded because it’s already in the browser’s cache.</p>
    <pre><code>&#x000A;    loadImage: function(image) {&#x000A;          var el, img,&#x000A;            _this = this;&#x000A;    &#x000A;          el = $("[data-src='" + image.og_src + "']");&#x000A;          &#x000A;          img = $("");&#x000A;          &#x000A;          img.attr("src", image.src).attr("alt", el.attr("data-alt"));&#x000A;          &#x000A;          if (el.children("img").length) {&#x000A;            el.children("img").attr("src", image.src);&#x000A;          } else {&#x000A;            img.load(function() {&#x000A;              el.append(img);&#x000A;              el.addClass('img-loaded');&#x000A;            });&#x000A;          }&#x000A;        }&#x000A;    </code></pre>
    <h3>PHP</h3>
    <p>With the JavaScript simply requesting an array of images at different sizes, the PHP is where the bulk of the action happens.</p>
    <p>We use two scripts. One is a <a href="https://github.com/gavmck/resize/blob/master/php/lib/resize-class.php" rel="nofollow external" class="bo"><code>resize</code> class</a> (found in <code>/php/lib/resize-class.php</code> in the demo), which creates cached versions of the image at the sizes we need. The <a href="https://github.com/gavmck/resize/blob/master/resize.php" rel="nofollow external" class="bo">other script</a> sits in the Web root, calculates the most appropriate size to display, and acts as an interface between the JavaScript and the resizer.</p>
    <p>Starting with the sizing and interface script, we first set an array of pixel sizes of the images that we expect to display, as well as the path to the cached images folder. The image sizes are in pixels because the server doesn’t know anything about the user’s current text-zoom level, only what the physical image sizes being served are.</p>
    <pre><code>&#x000A;    $sizes = array(&#x000A;        '100',&#x000A;        '300',&#x000A;        '600',&#x000A;        '1200',&#x000A;        '1500',&#x000A;    );&#x000A;    &#x000A;    $cache = 'img/cache/';&#x000A;    </code></pre>
    <p>Next, we create a small function that returns the image size closest to the current display size.</p>
    <pre><code>&#x000A;    function closest($search, $arr) {&#x000A;        $closest = null;&#x000A;        foreach($arr as $item) {&#x000A;            // distance from image width -&gt; current closest entry is greater than distance from  &#x000A;            if ($closest == null || abs($search - $closest) &gt; abs($item - $search)) {&#x000A;                $closest = $item;&#x000A;            }&#x000A;        }&#x000A;        $closest = ($closest == null) ? $closest = $search : $closest;&#x000A;        return $closest;&#x000A;    }&#x000A;    </code></pre>
    <p>Finally, we can loop through the image paths posted to the script and pass them to the <code>resize</code> class to get the path to the cached image file (and create that file, if necessary).</p>
    <pre><code>&#x000A;    $crispy = new resize($image,$width,$cache);&#x000A;    $newSrc = $crispy-&gt;resizeImage();&#x000A;    </code></pre>
    <p>We return the original image path in order to find the image again in the DOM and the path to the correctly sized cached image file. All of the image paths are sent back as an array so that we can loop through them and add them to the HTML.</p>
    <pre><code>&#x000A;    $images[] =  array('og_src' =&gt; $src, 'src' =&gt; '/'.$newSrc);&#x000A;    </code></pre>
    <p>In the <code>resize</code> class, we initially need to gather some information about the image for the resizing process. We use <code>Exif</code> to determine the type of image because the file could possibly have an incorrect extension or no extension at all.</p>
    <pre><code>&#x000A;    function __construct($fileName, $width, $cache) {&#x000A;    &#x000A;        $this-&gt;src = $fileName;&#x000A;        $this-&gt;newWidth = $width;&#x000A;        $this-&gt;cache = $cache;&#x000A;        $this-&gt;path = $this-&gt;setPath($width);&#x000A;    &#x000A;        $this-&gt;imageType = exif_imagetype($fileName);&#x000A;    &#x000A;        switch($this-&gt;imageType)&#x000A;        {&#x000A;            case IMAGETYPE_JPEG:&#x000A;                $this-&gt;path .= '.jpg';&#x000A;                break;&#x000A;    &#x000A;            case IMAGETYPE_GIF:&#x000A;                $this-&gt;path .= '.gif';&#x000A;                break;&#x000A;    &#x000A;            case IMAGETYPE_PNG:&#x000A;                $this-&gt;path .= '.png';&#x000A;                break;&#x000A;    &#x000A;            default:&#x000A;                // *** Not recognized&#x000A;                break;&#x000A;        }&#x000A;    }&#x000A;    </code></pre>
    <p>The <code>$this-&gt;path</code> property above, containing the cached image path, is set using a combination of the display width, a hash of the file’s last modified time and <code>src</code>, and the original file name.</p>
    <p>Upon calling the <code>resizeImage</code> method, we check to see whether the path set in <code>$this-&gt;path</code> already exists and, if so, we just return the cached file path.</p>
    <p>If the file does not exist, then we open the image with GD to be resized.</p>
    <p>Once it’s ready for use, we calculate the width-to-height ratio of the original image and use that to give us the height of the cached image after having been resized to the required width.</p>
    <pre><code>&#x000A;    if ($this-&gt;image) {&#x000A;        $this-&gt;width  = imagesx($this-&gt;image);&#x000A;        $this-&gt;height = imagesy($this-&gt;image);&#x000A;    }&#x000A;    &#x000A;    $ratio = $this-&gt;height/$this-&gt;width;&#x000A;    $newHeight = $this-&gt;newWidth*$ratio;&#x000A;    </code></pre>
    <p>Then, with GD, we resize the original image to the new dimensions and return the path of the cached image file to the interface script.</p>
    <pre><code>&#x000A;    $this-&gt;imageResized = imagecreatetruecolor($this-&gt;newWidth, $newHeight);&#x000A;    imagecopyresampled($this-&gt;imageResized, $this-&gt;image, 0, 0, 0, 0, $this-&gt;newWidth, $newHeight, $this-&gt;width, $this-&gt;height);&#x000A;    &#x000A;    $this-&gt;saveImage($this-&gt;newWidth);&#x000A;    &#x000A;    return $this-&gt;path;&#x000A;    </code></pre>
    <h3>What Have We Achieved?</h3>
    <p>This plugin enables us to have one single batch of images for the website. We don’t have to think about how to resize images because the process is automated. This makes maintenance and updates much easier, and it removes a layer of thinking that is better devoted to more important tasks. Plug it in once and forget about it.</p>
    <p><abbr>TL;DR</abbr>? Let’s summarize the functionality once more for good measure.</p>
    <p>In our markup, we provide an image wrapper that contains a <code>&lt;noscript&gt;</code> fallback. This wrapper has a data attribute of our original high-resolution image for a reference. We use JavaScript to send an AJAX request to a PHP file on the server, asking for the correctly sized version of this image. The PHP file either resizes the image and delivers the path of the correctly sized image or just returns the path if the image has already been created. Once the AJAX request has been completed, we append the new image to the DOM, or we just update the <code>src</code> if one has already been added. If the user resizes their browser, then we check again to see whether a better image size should be used.</p>
    <h3>Pros And Cons</h3>
    <p>All responsive image solutions have their pros and cons, and you should investigate several before choosing one for your project. Ours happens to work for our very specific set of requirements, and it wouldn’t be our default solution. As far as we can tell, there is no default solution at the moment, so we’d recommend trying out as many as possible.</p>
    <p>How does this solution weigh up?</p>
    <h4>Pros</h4>
    <ul>
    <li>Fast initial page download due to lower image weight</li>
    <li>Easy to use once set up</li>
    <li>Low maintenance</li>
    <li>Fast once cached files have been created</li>
    <li>Serves image at correct pixel size (within tolerance)</li>
    <li>Serves new image when display size changes (within tolerance)</li>
    </ul>
    <h4>Cons</h4>
    <ul>
    <li>Unable to choose image focus area</li>
    <li>Requires PHP and JavaScript for full functionality</li>
    <li>Can’t cover all possible image sizes if fluid images are used</li>
    <li>Might not be compatible with some content management systems</li>
    <li>Resizing all images with one request means that, with an empty cache, you have to wait for all to be resized, rather than just one image</li>
    <li>The PHP script is tied to breakpoints, so it can’t be dropped in without tweaking</li>
    </ul>
    <p>Responsive image solutions have come a long way in recent months, and if we had to do this again, we’d probably look at something like the <a href="http://adaptive-images.com" rel="nofollow external" class="bo">adaptive images</a> solution because it removes even more non-semantic HTML from the page by modifying <code>.htaccess</code>.</p>
    <h3>Wrapping Up</h3>
    <p>Until we have a native solution for responsive images, there will be no “right” way. Always investigate several options before settling on one for your project. The example here works well for websites with a few common display sizes for images across breakpoints, but it is by no means the definitive solution. Until then, why not have a go at creating your own solution, or play around with this one <a href="https://github.com/gavmck/resize" title="Resize on GitHub" rel="nofollow external" class="bo">on GitHub</a>?</p>
    <p><em>(al, il)</em></p>
    <p><em>SmashingMag front page image credits: <a href="http://phosho.co/" rel="nofollow external" class="bo">PhoSho's front page showcase</a>.</em></p>
    <hr>
    <p><small>© Gavyn McKenzie for <a href="http://www.smashingmagazine.com" rel="nofollow external" class="bo">Smashing Magazine</a>, 2014.</small></p>
    </div>
]]>
</Body>
<Summary>        Responsive images have been, and are, one of the hardest problems in responsive Web design right now. Until browser vendors give us a native solution, we have to think on the fly and come...</Summary>
<Website>http://www.smashingmagazine.com/2014/02/03/one-solution-to-responsive-images/</Website>
<TrackingUrl>https://my3.my.umbc.edu/api/v0/pixel/news/40908/guest@my.umbc.edu/a50bf569bd63b2250083a4cdabb2f001/api/pixel</TrackingUrl>
<Tag>css</Tag>
<Tag>design</Tag>
<Tag>development</Tag>
<Tag>html</Tag>
<Tag>javascript</Tag>
<Tag>mobile</Tag>
<Tag>mysql</Tag>
<Tag>php</Tag>
<Tag>sql</Tag>
<Tag>web</Tag>
<Group token="retired-583">Web Developer - Build Group</Group>
<GroupUrl>https://my3.my.umbc.edu/groups/retired-583</GroupUrl>
<AvatarUrl>https://assets3-my.umbc.edu/system/shared/avatars/groups/000/000/583/fc60f5d7abc2e080599bb6dc465db54d/xsmall.png?1363101197</AvatarUrl>
<AvatarUrl size="original">https://assets4-my.umbc.edu/system/shared/avatars/groups/000/000/583/fc60f5d7abc2e080599bb6dc465db54d/original.jpg?1363101197</AvatarUrl>
<AvatarUrl size="xxlarge">https://assets4-my.umbc.edu/system/shared/avatars/groups/000/000/583/fc60f5d7abc2e080599bb6dc465db54d/xxlarge.png?1363101197</AvatarUrl>
<AvatarUrl size="xlarge">https://assets4-my.umbc.edu/system/shared/avatars/groups/000/000/583/fc60f5d7abc2e080599bb6dc465db54d/xlarge.png?1363101197</AvatarUrl>
<AvatarUrl size="large">https://assets3-my.umbc.edu/system/shared/avatars/groups/000/000/583/fc60f5d7abc2e080599bb6dc465db54d/large.png?1363101197</AvatarUrl>
<AvatarUrl size="medium">https://assets3-my.umbc.edu/system/shared/avatars/groups/000/000/583/fc60f5d7abc2e080599bb6dc465db54d/medium.png?1363101197</AvatarUrl>
<AvatarUrl size="small">https://assets4-my.umbc.edu/system/shared/avatars/groups/000/000/583/fc60f5d7abc2e080599bb6dc465db54d/small.png?1363101197</AvatarUrl>
<AvatarUrl size="xsmall">https://assets3-my.umbc.edu/system/shared/avatars/groups/000/000/583/fc60f5d7abc2e080599bb6dc465db54d/xsmall.png?1363101197</AvatarUrl>
<AvatarUrl size="xxsmall">https://assets4-my.umbc.edu/system/shared/avatars/groups/000/000/583/fc60f5d7abc2e080599bb6dc465db54d/xxsmall.png?1363101197</AvatarUrl>
<Sponsor>Web Developer - Build Group</Sponsor>
<PawCount>0</PawCount>
<CommentCount>0</CommentCount>
<CommentsAllowed>true</CommentsAllowed>
<PostedAt>Mon, 03 Feb 2014 07:48:06 -0500</PostedAt>
</NewsItem>

<NewsItem contentIssues="true" id="40909" important="false" status="posted" url="https://my3.my.umbc.edu/posts/40909">
<Title>Bits Blog: Tech&#8217;s Diversity Problem Is Apparent as Early as High School</Title>
<Body>
<![CDATA[
    <div class="html-content">In several states, no girls, black or Hispanic students took the Advanced Placement exam in computer science last year, illustrating the lack of diversity in the tech industry.<br><div><table border="0"><tbody><tr><td>
    <a href="http://share.feedsportal.com/share/twitter/?u=http%3A%2F%2Fbits.blogs.nytimes.com%2F2014%2F02%2F02%2Ftechs-diversity-problem-is-apparent-as-early-as-high-school%2F%3Fpartner%3Drss%26emc%3Drss&amp;t=Bits+Blog%3A+Tech%E2%80%99s+Diversity+Problem+Is+Apparent+as+Early+as+High+School" rel="nofollow external" class="bo"><img src="http://res3.feedsportal.com/social/twitter.png" style="max-width: 100%; height: auto;"></a> <a href="http://share.feedsportal.com/share/facebook/?u=http%3A%2F%2Fbits.blogs.nytimes.com%2F2014%2F02%2F02%2Ftechs-diversity-problem-is-apparent-as-early-as-high-school%2F%3Fpartner%3Drss%26emc%3Drss&amp;t=Bits+Blog%3A+Tech%E2%80%99s+Diversity+Problem+Is+Apparent+as+Early+as+High+School" rel="nofollow external" class="bo"><img src="http://res3.feedsportal.com/social/facebook.png" style="max-width: 100%; height: auto;"></a> <a href="http://share.feedsportal.com/share/linkedin/?u=http%3A%2F%2Fbits.blogs.nytimes.com%2F2014%2F02%2F02%2Ftechs-diversity-problem-is-apparent-as-early-as-high-school%2F%3Fpartner%3Drss%26emc%3Drss&amp;t=Bits+Blog%3A+Tech%E2%80%99s+Diversity+Problem+Is+Apparent+as+Early+as+High+School" rel="nofollow external" class="bo"><img src="http://res3.feedsportal.com/social/linkedin.png" style="max-width: 100%; height: auto;"></a> <a href="http://share.feedsportal.com/share/gplus/?u=http%3A%2F%2Fbits.blogs.nytimes.com%2F2014%2F02%2F02%2Ftechs-diversity-problem-is-apparent-as-early-as-high-school%2F%3Fpartner%3Drss%26emc%3Drss&amp;t=Bits+Blog%3A+Tech%E2%80%99s+Diversity+Problem+Is+Apparent+as+Early+as+High+School" rel="nofollow external" class="bo"><img src="http://res3.feedsportal.com/social/googleplus.png" style="max-width: 100%; height: auto;"></a> <a href="http://share.feedsportal.com/share/email/?u=http%3A%2F%2Fbits.blogs.nytimes.com%2F2014%2F02%2F02%2Ftechs-diversity-problem-is-apparent-as-early-as-high-school%2F%3Fpartner%3Drss%26emc%3Drss&amp;t=Bits+Blog%3A+Tech%E2%80%99s+Diversity+Problem+Is+Apparent+as+Early+as+High+School" rel="nofollow external" class="bo"><img src="http://res3.feedsportal.com/social/email.png" style="max-width: 100%; height: auto;"></a>
    </td></tr></tbody></table></div>
    <br><br><a href="http://da.feedsportal.com/r/186530151940/u/0/f/640387/c/34625/s/36a8397f/sc/15/rc/1/rc.htm" rel="nofollow external" class="bo"><img src="http://da.feedsportal.com/r/186530151940/u/0/f/640387/c/34625/s/36a8397f/sc/15/rc/1/rc.img" style="max-width: 100%; height: auto;"></a><br><a href="http://da.feedsportal.com/r/186530151940/u/0/f/640387/c/34625/s/36a8397f/sc/15/rc/2/rc.htm" rel="nofollow external" class="bo"><img src="http://da.feedsportal.com/r/186530151940/u/0/f/640387/c/34625/s/36a8397f/sc/15/rc/2/rc.img" style="max-width: 100%; height: auto;"></a><br><a href="http://da.feedsportal.com/r/186530151940/u/0/f/640387/c/34625/s/36a8397f/sc/15/rc/3/rc.htm" rel="nofollow external" class="bo"><img src="http://da.feedsportal.com/r/186530151940/u/0/f/640387/c/34625/s/36a8397f/sc/15/rc/3/rc.img" style="max-width: 100%; height: auto;"></a><br><br><a href="http://da.feedsportal.com/r/186530151940/u/0/f/640387/c/34625/s/36a8397f/a2.htm" rel="nofollow external" class="bo"><img src="http://da.feedsportal.com/r/186530151940/u/0/f/640387/c/34625/s/36a8397f/a2.img" style="max-width: 100%; height: auto;"></a>
    </div>
]]>
</Body>
<Summary>In several states, no girls, black or Hispanic students took the Advanced Placement exam in computer science last year, illustrating the lack of diversity in the tech industry.      </Summary>
<Website>http://bits.blogs.nytimes.com/2014/02/02/techs-diversity-problem-is-apparent-as-early-as-high-school/?partner=rss&amp;emc=rss</Website>
<TrackingUrl>https://my3.my.umbc.edu/api/v0/pixel/news/40909/guest@my.umbc.edu/cf8ebcf3f7795f7627908dfac60aacab/api/pixel</TrackingUrl>
<Tag>blacks</Tag>
<Tag>census-bureau</Tag>
<Tag>children</Tag>
<Tag>college-board</Tag>
<Tag>computers-and-the-internet</Tag>
<Tag>education-k-12</Tag>
<Tag>equal-educational-opportunities</Tag>
<Tag>hispanic-americans</Tag>
<Tag>new</Tag>
<Tag>policy</Tag>
<Tag>technology</Tag>
<Tag>tests-and-examinations</Tag>
<Tag>women-and-girls</Tag>
<Tag>york</Tag>
<Group token="retired-583">Web Developer - Build Group</Group>
<GroupUrl>https://my3.my.umbc.edu/groups/retired-583</GroupUrl>
<AvatarUrl>https://assets3-my.umbc.edu/system/shared/avatars/groups/000/000/583/fc60f5d7abc2e080599bb6dc465db54d/xsmall.png?1363101197</AvatarUrl>
<AvatarUrl size="original">https://assets4-my.umbc.edu/system/shared/avatars/groups/000/000/583/fc60f5d7abc2e080599bb6dc465db54d/original.jpg?1363101197</AvatarUrl>
<AvatarUrl size="xxlarge">https://assets4-my.umbc.edu/system/shared/avatars/groups/000/000/583/fc60f5d7abc2e080599bb6dc465db54d/xxlarge.png?1363101197</AvatarUrl>
<AvatarUrl size="xlarge">https://assets4-my.umbc.edu/system/shared/avatars/groups/000/000/583/fc60f5d7abc2e080599bb6dc465db54d/xlarge.png?1363101197</AvatarUrl>
<AvatarUrl size="large">https://assets3-my.umbc.edu/system/shared/avatars/groups/000/000/583/fc60f5d7abc2e080599bb6dc465db54d/large.png?1363101197</AvatarUrl>
<AvatarUrl size="medium">https://assets3-my.umbc.edu/system/shared/avatars/groups/000/000/583/fc60f5d7abc2e080599bb6dc465db54d/medium.png?1363101197</AvatarUrl>
<AvatarUrl size="small">https://assets4-my.umbc.edu/system/shared/avatars/groups/000/000/583/fc60f5d7abc2e080599bb6dc465db54d/small.png?1363101197</AvatarUrl>
<AvatarUrl size="xsmall">https://assets3-my.umbc.edu/system/shared/avatars/groups/000/000/583/fc60f5d7abc2e080599bb6dc465db54d/xsmall.png?1363101197</AvatarUrl>
<AvatarUrl size="xxsmall">https://assets4-my.umbc.edu/system/shared/avatars/groups/000/000/583/fc60f5d7abc2e080599bb6dc465db54d/xxsmall.png?1363101197</AvatarUrl>
<Sponsor>Web Developer - Build Group</Sponsor>
<PawCount>0</PawCount>
<CommentCount>0</CommentCount>
<CommentsAllowed>true</CommentsAllowed>
<PostedAt>Mon, 03 Feb 2014 07:42:19 -0500</PostedAt>
<EditAt>Mon, 03 Feb 2014 11:14:24 -0500</EditAt>
</NewsItem>

<NewsItem contentIssues="false" id="40907" important="false" status="posted" url="https://my3.my.umbc.edu/posts/40907">
<Title>Due to current weather conditions, classes at our Columbia location will have a...</Title>
<Body>
<![CDATA[
    <div class="html-content">Due to current weather conditions, classes at our Columbia location will have a delayed opening for 11:00 a.m.</div>
]]>
</Body>
<Summary>Due to current weather conditions, classes at our Columbia location will have a delayed opening for 11:00 a.m.</Summary>
<Website>http://www.facebook.com/umbctraining/posts/10151839692216076</Website>
<TrackingUrl>https://my3.my.umbc.edu/api/v0/pixel/news/40907/guest@my.umbc.edu/38eeaa16f8512595c0d004650c7c240a/api/pixel</TrackingUrl>
<Tag>ccna</Tag>
<Tag>ceh</Tag>
<Tag>centers</Tag>
<Tag>cisco</Tag>
<Tag>cyber</Tag>
<Tag>cybersecurity</Tag>
<Tag>information</Tag>
<Tag>it</Tag>
<Tag>leadership</Tag>
<Tag>management</Tag>
<Tag>microsoft</Tag>
<Tag>project</Tag>
<Tag>security</Tag>
<Tag>technology</Tag>
<Tag>training</Tag>
<Tag>umbc</Tag>
<Group token="retired-575">UMBC Training Centers</Group>
<GroupUrl>https://my3.my.umbc.edu/groups/retired-575</GroupUrl>
<AvatarUrl>https://assets1-my.umbc.edu/system/shared/avatars/groups/000/000/575/83756b985266168d0d29c6c9a146db50/xsmall.png?1361981335</AvatarUrl>
<AvatarUrl size="original">https://assets4-my.umbc.edu/system/shared/avatars/groups/000/000/575/83756b985266168d0d29c6c9a146db50/original.jpg?1361981335</AvatarUrl>
<AvatarUrl size="xxlarge">https://assets3-my.umbc.edu/system/shared/avatars/groups/000/000/575/83756b985266168d0d29c6c9a146db50/xxlarge.png?1361981335</AvatarUrl>
<AvatarUrl size="xlarge">https://assets4-my.umbc.edu/system/shared/avatars/groups/000/000/575/83756b985266168d0d29c6c9a146db50/xlarge.png?1361981335</AvatarUrl>
<AvatarUrl size="large">https://assets1-my.umbc.edu/system/shared/avatars/groups/000/000/575/83756b985266168d0d29c6c9a146db50/large.png?1361981335</AvatarUrl>
<AvatarUrl size="medium">https://assets2-my.umbc.edu/system/shared/avatars/groups/000/000/575/83756b985266168d0d29c6c9a146db50/medium.png?1361981335</AvatarUrl>
<AvatarUrl size="small">https://assets4-my.umbc.edu/system/shared/avatars/groups/000/000/575/83756b985266168d0d29c6c9a146db50/small.png?1361981335</AvatarUrl>
<AvatarUrl size="xsmall">https://assets1-my.umbc.edu/system/shared/avatars/groups/000/000/575/83756b985266168d0d29c6c9a146db50/xsmall.png?1361981335</AvatarUrl>
<AvatarUrl size="xxsmall">https://assets2-my.umbc.edu/system/shared/avatars/groups/000/000/575/83756b985266168d0d29c6c9a146db50/xxsmall.png?1361981335</AvatarUrl>
<Sponsor>UMBC Training Centers</Sponsor>
<PawCount>0</PawCount>
<CommentCount>1</CommentCount>
<CommentsAllowed>true</CommentsAllowed>
<PostedAt>Mon, 03 Feb 2014 06:18:05 -0500</PostedAt>
<EditAt>Mon, 03 Feb 2014 06:18:05 -0500</EditAt>
</NewsItem>

</News>
