pixelhandler.devSoftware engineering blog by Billy Heaton. Articles about Ruby on Rails, JavaScript, Ember.js, and web development best practices.https://pixelhandler.dev/2026-03-05T00:00:00+00:00Billy Heaton[email protected]https://pixelhandler.devSystems of Building, Not Building Systemshttps://pixelhandler.dev/posts/systems-of-building2026-03-05T00:00:00+00:002026-03-05T00:00:00+00:00Billy HeatonOn containment, focus, and the practice of examining how you work<h2>Systems of Building, Not Building Systems</h2>
<p><strong>On containment, focus, and the practice of examining how you work</strong></p>
<p>The tools are moving fast. Faster than any stretch I can remember — and I've been through a few cycles. Learn the new thing, adapt, keep building.</p>
<p>This time feels different. It's not a new framework or editor. <em>It's a new relationship with tools entirely.</em> I have an AI pair that can read my codebase, run my tests, and draft a pull request while I think through the next problem. That changes things — not just what I can build, but how I think about building.</p>
<p>Somewhere in the past year I noticed I was spending less time learning new libraries and more time thinking about process. How do I structure a work session? How do I make sure the code that gets generated actually holds up? How do I stay focused when the tooling makes it easy to scatter?</p>
<p>I think more about systems of building than building systems. There's a paradox in that. I spent years focused on designing and building systems — the software itself, the architecture, the data models. Now I find myself designing systems <em>of</em> building — the workflows, the feedback loops, the practices that shape how things get made. The craft evolved from what I build to how I build it.</p>
<h2>Containment Enables Quality</h2>
<p>That phrase — <em>containment enables quality</em> — comes from how Stripe designed their coding agents. They put AI into quarantined environments with clear boundaries: one task, one branch, one set of constraints. The containment isn't a limitation. It's what makes quality possible.</p>
<p>I've been applying that idea to my own workflow at a personal scale, across three <em>dimensions</em>.</p>
<h3>Containing Conventions</h3>
<p>Most teams have conventions. They live in a wiki, or an onboarding doc, or in the heads of senior engineers. The problem is that none of those places are where you're actually working. When you're in the middle of a commit or structuring a PR, the conventions aren't present.</p>
<p>I started encoding my development lifecycle — plan, implement, test, commit, PR, review — into a skill file that my AI tooling reads automatically. Not aspirational process. Honest process. How I actually work, what I actually name things, where artifacts actually go.</p>
<p>The effect is consistency without willpower. The conventions show up at the moment they matter — when I'm planning, when I'm committing, when I'm reviewing. They evolve as I learn. When I discover a better pattern, I update the file. It's a living record, not a frozen document.</p>
<p>The key insight: conventions that live where the AI can apply them are conventions that actually get followed. Everything else is aspiration.</p>
<h3>Containing Attention</h3>
<p><em>The second dimension is focus.</em> Creating room for deep work on a single problem, without context-switching contamination.</p>
<p>I set up isolated environments per issue — a fresh clone, unique ports, a separate database. The skill file scopes the session to <em>this</em> ticket, <em>this</em> plan, <em>this</em> branch. Everything outside that boundary disappears. Spin up an environment, do the work, tear it down.</p>
<p>This isn't about delegating work to AI. It's about creating the conditions where you — with AI — can do focused, uninterrupted work on one thing. The isolation removes the temptation to check on other branches, fix that unrelated bug, or chase a tangent.</p>
<p>Creating room for focus is a deliberate engineering practice, not an accident. <strong>You have to build it.</strong></p>
<h3>Containing Risk</h3>
<p><em>The third dimension is quality assurance</em> — making sure that what gets built actually works as intended. Not "the tests pass" quality. Visible, verifiable proof that a solution solves the problem it claims to solve.</p>
<p>I picked up browser automation recently, learning Playwright as a new skill. The motivation wasn't testing for testing's sake. It was a desire to tip the balance of AI-assisted development toward delivering quality solutions. When code gets generated quickly, the risk shifts from "can I build this?" to "does this actually work the way users expect?" A passing test suite isn't enough to answer that question. <em>You need evidence</em> — screenshots that show the before and after, recordings that walk through the behavior, assertions that verify specific claims about what the user sees. The goal is a body of proof that you can point to and say: this works, and here's how I know.</p>
<p>Outside-in test-driven development became the discipline. The idea is simple: instead of starting with the smallest unit and building up, you start from the outermost layer — what the user actually sees — and work inward. As Harry Percival describes in <em>Test-Driven Development with Python</em>:</p>
<blockquote>
<p>Working outside-in enables you to use each layer to imagine the most convenient API you could want from the layer beneath it.</p>
</blockquote>
<p>Percival also makes the connection to AI-assisted development:</p>
<blockquote>
<p>The best way to work with AI, you'll find that it performs best when working in small, well-defined contexts, with frequent checks for correctness.</p>
</blockquote>
<p>And the classic TDD cycle — red, green, refactor — takes on new weight. As Percival puts it, you need:</p>
<blockquote>
<p>The "refactor" step to try and improve the often-terrible code that the AI produced.</p>
</blockquote>
<p>Working with generated code means more refactoring, not less. Automated PR reviews surface issues early, which leads to another round of cleanup. The cycle tightens: test, implement, refactor, review, refactor again. It's more work on the quality side, but that's exactly where the work belongs.</p>
<p>There's something else I didn't expect. Using Playwright to drive the browser — watching it click through flows, fill forms, verify results — is a refreshing break from console-based REPL (read–eval–print loop) cycles. After spending so much time in the terminal, seeing the browser do the work is wonderful. It reminded me why I got into this craft in the first place: building things people can see and interact with.</p>
<p>Write a failing test that asserts the behavior you want from the user's perspective. Implement until it passes. Then verify with evidence — not just green checkmarks, but proof.</p>
<p>Learning browser automation gave me new skills I didn't have before — screenshot comparison across branches, scripted demo recordings, and automated smoke testing. These aren't just testing techniques. They're tools for building evidence. Three layers of evidence changed how I think about quality:</p>
<p><strong>Behavioral assertions</strong> — automated smoke tests that walk through user workflows. Does the record appear in the list? Does the form save correctly? Does the filter show the right results? Each assertion answers a specific question about the application's behavior.</p>
<p><strong>Visual screenshots</strong> — captured against both the main branch and the feature branch, then compared side by side. You can see exactly what changed. Unintended regressions show up immediately. The visual diff is honest in a way that passing tests alone aren't.</p>
<p><strong>Demo recordings</strong> — scripted video walkthroughs that document how the feature actually behaves. These serve as proof artifacts for PR review and stakeholder communication. When someone asks "what does this change do?" you have a recording, not just a description.</p>
<p>The outside-in workflow has natural checkpoints — moments where you pause, review, and confirm before moving forward. Capture the baseline. Review the failing tests. Verify they pass. Compare the screenshots. Each checkpoint is a decision point: does this look right so far?</p>
<p>Some lessons came the hard way. You learn by doing, and the doing reveals assumptions you didn't know you had. The testing practice forces you to understand how your application actually works, not just how you think it works.</p>
<h2>In Practice: Outside-In Development with AI</h2>
<p>Here's the current iteration of my workflow — what I actually follow today:</p>
<pre class="highlight"><code>Ticket
→ Plan
→ Implementation (feature branch, TDD)
→ Test Data Script
→ Manual Testing (localhost, step-by-step with URLs)
→ E2E Testing (automate manual test scenarios)
→ Assertion tests (verify expected behavior)
→ Visual comparison (screenshots: main vs feature branch)
→ Demo recording (stakeholder walkthrough video)
→ Commit (specific files, descriptive message)
→ Push + Draft PR
→ Self-Review (read findings, do not post)
→ PR Comments (annotate hard-to-understand areas)
→ Team Review → Address Feedback → Merge
Outside-In TDD variant (for UI changes):
→ Plan
→ Phase 0: E2E Test Triage (new file vs existing)
→ Phase 1: Test Data Setup
→ Phase 2: Baseline Screenshots
── CHECKPOINT: review baseline ──
→ Phase 3: Failing E2E Tests
── CHECKPOINT: review failing output ──
→ Phase 4: Implement
→ Phase 5: Verify E2E Tests Pass
── CHECKPOINT: review passing output ──
→ Phase 6: Screenshot Comparison
→ Phase 7: Unit/Integration Tests
→ Phase 8: Demo Recording
→ Phase 9: Update Plan Doc
→ Commit → PR → Review
</code></pre>
<p>Browser automation brought these QA practices into my AI workflow — each one solving a real problem:</p>
<table><thead>
<tr>
<th>Problem</th>
<th>Solution</th>
</tr>
</thead><tbody>
<tr>
<td>Manual QA is slow and unrepeatable</td>
<td>Automated smoke tests run the same checks every time</td>
</tr>
<tr>
<td>No visual regression detection</td>
<td>Screenshot comparison captures baseline and current states for side-by-side diff</td>
</tr>
<tr>
<td>Feature demos are ad hoc</td>
<td>Scripted demo recordings produce consistent walkthroughs</td>
</tr>
<tr>
<td>Bug hunting requires setup</td>
<td>Bug reproduction specs encode the exact steps to reproduce</td>
</tr>
<tr>
<td>No outside-in acceptance gate</td>
<td>Write a failing Playwright test before implementation, then make it pass</td>
</tr>
<tr>
<td>Cross-branch comparison is manual</td>
<td>Run dev servers on different ports per clone and compare side-by-side</td>
</tr>
</tbody></table>
<p>With modern tooling — where AI can generate implementation code rapidly — outside-in discipline matters even more. It's what keeps the speed honest. Without it, you're shipping fast but blind. I can point AI agents at my running application and let them drive the browser — clicking through flows, capturing screenshots, hunting for regressions. During bug hunting, I reproduce issues systematically and verify fixes visually. During feature development, I have proof artifacts before I ever open a pull request.</p>
<p>The iteration of writing scratchpads — markdown files where I journal the problem, sketch the approach, document what I tried, record what I learned — that iteration <em>is</em> the system. Each scratchpad becomes a reference. Each reference shapes the next session. The workflow isn't a fixed process I follow. It's a living practice that evolves every time I sit down to work. I expect it will keep evolving.</p>
<h2>The Practice</h2>
<p>The tools will keep changing. The frameworks, the AI capabilities, the testing libraries — all of it will be different in a year. What compounds isn't knowledge of any specific tool. <em>It's the habit of examining how you work.</em></p>
<p>I journal a lot — in notebooks, in markdown files, in plan documents that start as scratch and evolve into reference material. The skill file itself is a kind of journal. It records how I work today, and the diff over time shows how my thinking has changed.</p>
<p>Being comfortable with re-thinking is part of the practice. Adam Grant's <em>Think Again</em> is built around this idea — that the willingness to reconsider what you know is more valuable than the knowledge itself. A workflow that felt right three months ago might need revision. A convention you were sure about might not hold up under new constraints. That's fine.</p>
<p>As Brené Brown put it:</p>
<blockquote>
<p>"Yes, learning requires focus. But, unlearning and relearning requires much more — it requires choosing courage over comfort."</p>
</blockquote>
<p>That resonates. The small adjustments — observing your own behavior, noticing what's working and what isn't, journaling the process — those take more courage than learning a new tool. The point isn't to arrive at a perfect system. The point is to keep examining, keep adjusting, and enjoy the journey.</p>
<p>Freestyle skier and physicist Eileen Gu put it better than I can:</p>
<blockquote>
<p>"I'm very introspective. I spend a lot of time in my head, and it's not a bad place to be. I journal a lot. I break down all of my thought processes. I think I apply a very analytical lens to my own thinking, and I kind of modify it because it's so interesting. You can control what you think. You can control how you think, and therefore, you can control who you are."</p>
</blockquote>
<p>That's the real system of building. Not the tools, not the workflow, not the skill file — though those matter. It's the willingness to look at how you think, break it down, and modify it. The containment that matters most is the one you apply to your own thinking: scoping it, examining it, refining it.</p>
<p>The tools will keep evolving. Build the practice of examining how you use them, and the rest follows.</p>
Turning Your Spreadsheet Budget into a High-Performance Gearboxhttps://pixelhandler.dev/posts/gnarli-budget-app-replaced-my-spreadsheets2026-01-20T00:00:00+00:002026-01-20T00:00:00+00:00Billy HeatonHow Gnar.li Budget lets DIY budgeters keep total control while ditching the copy-paste grind<h2>Turning Your Spreadsheet Budget into a High-Performance Gearbox</h2>
<p><strong>How Gnar.li Budget lets DIY budgeters keep total control while ditching the copy-paste grind</strong></p>
<p>There is a particular kind of satisfaction that comes from a well-organized spreadsheet. Every dollar mapped to a row. Every formula aligned. The numbers reconcile, and for a moment, you feel completely in command of your finances.</p>
<p>Then month two rolls around. And month three. And suddenly that satisfaction curdles into something else: the copy-paste grind.</p>
<h2>The Spreadsheet Trap</h2>
<p>I've spent years building financial models in Excel and Google Sheets. I love the visibility, the control, the ability to trace exactly where every dollar flows. But somewhere around the tenth monthly import, the ritual started feeling less like financial mastery and more like data entry purgatory:</p>
<p><strong>Manual CSV imports</strong> — dragging a bank export into a sheet, hunting for the right column, fixing date formats, hoping I didn't shift a decimal.</p>
<p><strong>Error-prone categorization</strong> — one typo in a category sends a whole batch of expenses to the abyss.</p>
<p><strong>Monthly maintenance overhead</strong> — rebuilding the same budget skeleton, reapplying the same formulas, wondering if I missed a line item somewhere in the chaos.</p>
<p>The spreadsheet gave me control, but it extracted a tax: my time, every single month.</p>
<h2>What If You Could Keep the Control and Ditch the Chores?</h2>
<p>That question led me to build <a href="proxy.php?url=https://budget.gnar.li" target="_blank" rel="noopener noreferrer">Gnar.li Budget</a> — a web app that lives inside the same mental model spreadsheet users already have, but automates the repetitive plumbing that never changes.</p>
<p>The philosophy is simple: <strong>every dollar gets a job. Capture, setup, review—a habit loop that keeps you in control of your money, not the other way around.</strong></p>
<p>Gnar.li Budget keeps that line-by-line visibility and explicit tagging you love. It just removes the copy-paste ceremony that makes budgeting feel like a chore.</p>
<h2>The Story Behind the Code</h2>
<p>Gnar.li Budget started as a Rails 8 prototype to scratch my own itch. Early on, I added the features I was missing in my spreadsheets:</p>
<p><strong>Eight-step onboarding</strong> that mirrors the spreadsheet setup you already perform — add accounts, map CSV columns, define categorization rules, schedule recurring bills, set a payroll-aligned budget period, then import your first batch of transactions.</p>
<p><strong>TagMatcher engine</strong> with 12+ default categorization rules you can edit, extend, or replace with regex patterns. They work like your custom IF statements, but you write them once and they apply forever.</p>
<p><strong>63+ default spending tags</strong> organized into seven groups (Income, Bills, Giving, Savings, Debt, Flexible Expenses, and Transfers) — the same categories you'd build in a spreadsheet, pre-configured and ready to use.</p>
<p><strong>Automatic transfer matching</strong> — the double-entry bookkeeping you manually enforce in a sheet now happens behind the scenes, keeping assets and liabilities balanced without extra rows.</p>
<p><strong>Scheduled and upcoming transactions</strong> — recurring bills appear as future rows so you can forecast cash flow without adding placeholder entries yourself.</p>
<p>Every piece is opt-in. If you prefer to hand-code a rule, you can. If you want the app to suggest a tag, it does so without overwriting your choice. The result is a zero-access, privacy-first environment that never reaches out to Plaid or any third-party aggregator. Your CSV files stay on your machine until you upload them. Your bank credentials are never shared.</p>
<h2>Feature-by-Feature: Spreadsheet to Gnar.li Budget</h2>
<p>Here's how each feature maps to the workflow you already know:</p>
<ul>
<li><strong>Manual CSV Import</strong> — replaces VLOOKUP column alignment, date format fixes, decimal hunting</li>
<li><strong>TagMatcher Rules</strong> — replaces reusable IF/THEN formulas that auto-categorize rows</li>
<li><strong>Transfer Matching</strong> — replaces manual double-entry rows for credit card payments and cash withdrawals</li>
<li><strong>Scheduled Transactions</strong> — replaces copy-paste placeholder lines for recurring bills</li>
<li><strong>Upcoming-Through Forecast</strong> — replaces adding a "future" column to project balances</li>
<li><strong>Budget Cloning</strong> — replaces copy-paste last month's budget rows into a new sheet</li>
<li><strong>Budget Averages Report</strong> — replaces AVERAGE formulas across historical spending to suggest amounts</li>
<li><strong>Snapshot Views</strong> — replaces filtered pivot tables for specific spending categories</li>
<li><strong>Insights Dashboard</strong> — replaces hand-built charts for monthly trends and category breakdowns</li>
<li><strong>Custom Reports</strong> — replaces building and exporting reports to CSV for downstream analysis</li>
<li><strong>Split Transactions</strong> — replaces manual row splitting when one purchase spans multiple categories</li>
<li><strong>Statement-Anchored Balances</strong> — replaces reconciliation formulas that anchor to bank statement closing balances</li>
<li><strong>Complete Data Isolation</strong> — each user's data is completely separated via authorization policies</li>
<li><strong>Multi-User Account Sharing</strong> — replaces emailing spreadsheet copies or sharing credentials; invite family or partners with View Only or Manage access levels</li>
</ul>
<h2>How the Workflow Actually Feels</h2>
<p><strong>Create a new budget</strong> — click "New Budget." The app automatically sets the date range based on your payroll schedule and clones the previous month's line items. No copy-paste required.</p>
<p><strong>Import your CSV</strong> — map the columns once (checking, credit, cash). The import runs TagMatcher, flags duplicates, and creates upcoming transactions for any future-dated rows.</p>
<p><strong>Tweak line items</strong> — just like editing a cell. Change any amount, and the app instantly recalculates totals, cash flow, and the projected balance.</p>
<p><strong>Add a one-off transaction</strong> — hit "Add Transaction," fill the fields, done. No need to open a separate sheet or scroll to the right row.</p>
<p><strong>Split or transfer</strong> — select a row, click "Split" or "Transfer," and the double-entry entries appear automatically. One $50 grocery trip that included $10 of household supplies? Split it in two clicks.</p>
<p><strong>Forecast forward</strong> — set the "upcoming through" date and watch projected balances extend into the future, exactly as if you'd added a "future" column in your sheet.</p>
<p><strong>Review insights</strong> — jump to the dashboard for a visual sanity check (monthly spending trends, top categories, income vs. expenses), then dive back into the transaction list to adjust any line.</p>
<p><strong>Share with family</strong> — invite a partner or family member to view or manage specific accounts. They see only what you share, with granular View Only or Manage permissions.</p>
<p>All of this happens without losing the audit trail. Every transaction still lives as a discrete record you can edit, delete, or recategorize at any time.</p>
<h2>The Gearbox Metaphor</h2>
<p>Think of Gnar.li Budget as adding a high-performance gearbox to the car you already built.</p>
<p>The <strong>engine</strong> — your spreadsheet mindset, your line-by-line control, your explicit tagging — stays exactly the same. You still decide every gear change.</p>
<p>The <strong>gearbox</strong> — Gnar.li Budget — handles the clutch work: syncing imports, auto-matching transfers, projecting cash flow, surfacing insights. All while keeping the driver firmly in control.</p>
<p>You're not handing over the wheel to an algorithm that decides where your money goes. You're offloading the repetitive plumbing so you can spend more time analyzing, optimizing, and iterating on your budget strategy.</p>
<h2>Learn the System: The "Every Dollar's Job" Course</h2>
<p>If you want a guided walkthrough, check out the free <strong><a href="proxy.php?url=https://budget.gnar.li/courses/every-dollars-job" target="_blank" rel="noopener noreferrer">Every Dollar's Job</a></strong> course — nine modules designed to mirror the workflow spreadsheet users already follow:</p>
<ol>
<li><strong>Preparation</strong> — gather take-home pay, fixed expenses, variable costs, and debt information</li>
<li><strong>Why a Budget Matters</strong> — the philosophy of zero-based budgeting</li>
<li><strong>Gather & Clean Data</strong> — import CSVs, set opening balances, create basic TagMatcher rules</li>
<li><strong>Build Your First Budget</strong> — use the Budget Averages Report to suggest amounts based on actual spending</li>
<li><strong>Fine-Tune TagMatcher</strong> — advanced rules with priority ordering and regex patterns</li>
<li><strong>Scheduled Transactions & Goals</strong> — automate recurring bills and budget for savings intentionally</li>
<li><strong>Daily Tracking</strong> — 2-5 minute check-ins using snapshot views and cash flow forecasting</li>
<li><strong>Insights Dashboard</strong> — analyze spending patterns, category trends, and income vs. expenses</li>
<li><strong>Sustain Your Budget</strong> — statement reconciliation, habit formation, and long-term maintenance</li>
</ol>
<p>Each module runs 10-15 minutes. Complete them at your own pace. By the end, you'll have a working budget system that runs faster than any spreadsheet you've built.</p>
<h2>Getting Started</h2>
<ol>
<li><strong>Sign up</strong> at <a href="proxy.php?url=https://budget.gnar.li" target="_blank" rel="noopener noreferrer">budget.gnar.li</a> — the first 100 monthly active users get free access</li>
<li><strong>Walk the 8-step onboarding</strong> — accounts, import maps, tag matchers, scheduled transactions, payroll schedule, budget, import</li>
<li><strong>Generate demo data</strong> — three months of realistic sample transactions to explore risk-free before importing your own</li>
<li><strong>Take the course</strong> — <a href="proxy.php?url=https://budget.gnar.li/courses/every-dollars-job" target="_blank" rel="noopener noreferrer">Every Dollar's Job</a> walks through the entire workflow</li>
</ol>
<h2>TL;DR</h2>
<p>You already know how to track every dollar. You've built the spreadsheets. You've written the formulas. You've done the work.</p>
<p>Gnar.li Budget keeps that control — the line-by-line visibility, the explicit tags, the audit trail — and adds automated CSV import, rule-based categorization, transfer matching, scheduled transactions, cash flow forecasting, and a clean insights dashboard.</p>
<p>Stop copy-pasting. Start analyzing.</p>
<p>Give it a spin, clone your first budget, and see how many minutes you shave off the monthly grind. The spreadsheet you love stays intact; the app just makes it run faster.</p>
<p>Happy budgeting!</p>
<hr>
<h2>Next Steps</h2>
<ul>
<li><strong>Join the beta</strong> — explore the onboarding and let me know which rule or report saved you the most time</li>
<li><strong>Share feedback</strong> — input from DIY budgeters like you shapes the next iteration</li>
<li><strong>Stay tuned</strong> on <a href="proxy.php?url=https://budget.gnar.li/insights" target="_blank" rel="noopener noreferrer">Gnar.li Budget Insights</a> for deeper dives into supported features</li>
</ul>
Remote Workhttps://pixelhandler.dev/posts/remote-work2020-03-10T00:00:00+00:002020-03-10T00:00:00+00:00Billy HeatonThis is what I propose as a distributed (remote) workplace manifesto...<p>This is what I propose as a distributed (remote) workplace manifesto...</p>
<ol>
<li>Results only work environment is the way of the remote worker, make that common.</li>
<li>Help everyone with meeting online, have a zoom # (video conference) in all meeting requests, have someone available on the Slack chat to reach out to be sure everyone can join, as a fallback use the dial-in phone number. Everyone can use Slack on their phones.</li>
<li>Meetings need a facilitator to be sure the agenda is on track and all voices have an opportunity to communicate, including onsite and distributed team members.</li>
<li>If it didn’t happen online, it didn’t happen. Use collaboration tools such as the Paper/Google Docs/Discourse/Notion to capture and share dialog – to provide easy access to meeting notes/agenda and take away actions.</li>
<li>Status meetings are for connecting not just status, come prepared. It’s a good idea to copy status into a Slack chat before/after the meeting - anyone can do this, and everyone benefits.</li>
<li>Flying solo is not sustainable (like scuba diving alone is not safe). Remote team members should have a pair whom they can collaborate with; someone who understands how to work remotely or is willing to learn. Remotes can help demonstrate how.</li>
<li>Favor asynchronous communication, when synchronous dialog is needed, send a calendar invite.</li>
<li>Being inclusive of remotes is encouraging diversity. A good idea can come from anywhere and anyone, a diversity of ideas and workplace experiences strengthens culture.</li>
<li>Build a rapport with the team. During the 1st year, a remote team member should work onsite 2-4 weeks. Bond in real life.</li>
<li>Deal direct, don’t save a conversation for someday down the road. Remote collaboration does not come without challenges, keep the discussion open among team members.</li>
<li>Empathy is key, we don’t know others’ situations - so be sure to understand, use video calls (zoom) often as you need. Much of our communication is non-verbal; and can be done remotely, not just in-person. By understanding another person’s situation, we can avoid the “fundamental attribution error.”</li>
<li>Say thank you, remote team members miss the “water cooler” chatter; which is good for focus. A quick “thank you” helps recognize effort, commitment and results – it’s an easy way to make a connection. Do it with Tacos in Slack as well.</li>
<li>Be patient. “If a thing is worth doing, it is worth doing badly.” Skills are sharpened by practice.</li>
</ol>
<p>For the occasional work from home experience (flexible schedule) not all of the above apply. Below are a few tips...</p>
<ul>
<li>Not every meeting needs to include video (or face-to-face) interactions. Make sure you keep communication open and timely.
For online meetings set a backup plan, perhaps the primary tool will be an online meeting and the backup may be a conference (call-in) phone number. Decide this in advance of the meeting start time</li>
<li>Show up and dress for work, wherever that is. Choosing where to work affords who has the privilege of interruption. Working at home can mean that loved ones are close by; don’t shut them out, agree on a time to connect. And give them some hugs too.</li>
</ul>
<p>Working remotely does come with some challenges and an adjustment for both onsite and remote individuals.</p>
<p>Perhaps one of the more difficult parts is for people who are accustomed to a realtime synchronous conversation (shoulder to shoulder). The minor audio delay in zoom meetings throws them off. Most audio/video conferencing tools have a slight audio delay. Knowing when to interject or speak up can be a challenge. A moderator can help, a visual signal can help too - 1 finger up for next, 2 fingers up for 2nd. Finding a place to meet via zoom is a hurdle for HQ desk workers. Onsite team members can help by using a good headset and overcoming hurdles of chatting via zoom while at their own desk. they're not interrupting (local) coworkers when meeting online with a (remote) coworker at their own desk.</p>
<p>One thing I advocate is taking breaks, a change of context can be valuable. I take a walk to get away from my laptop. Over the years, I’ve walked and talked when having tough conversations. Walking raises my heart rate, increases blood flow and causes changes in my breathing pattern, which forces me to focus and choose my words carefully.</p>
<p>For those with loved ones at home… the biggest adjustment is not just you working from home. The adjustment may be difficult for loved ones who’s home has been interrupted with work. If you have a door use that when you are in a meeting or need focus; otherwise, keep the door open for loved ones to come and visit with you. It’s ok to be present at home. I would listen as my kids stopped by to chat, validating their wishes. At times a pet would accompany me while I work, even in meetings. One thing I practice for my little ones… ask them what they wanted to do after lunch, so they knew I would make time for them. Being present lets them know you care. Helping your family with expectations helps them adjust to you working in the home. It’s also very rewarding, one day they may no longer be close by - enjoy the people close to you.</p>
<p>Regardless of working onsite or remotely, one important concept that I advocate for is... I share with my team - how to work with me. What my hours are, how best to communicate, e.g. slack, calendar invite, text/call, and my cell phone number. I tend to over-communicate as well. I’ve learned that people are not always tuned in, so it’s ok to repeat your message. But any communication that seems lengthy when typing out worth meeting face to face, rather than a chat or email.</p>
<p>As for tools, a good headset and microphone make a big difference when using online conferencing tools. I use Beats noise-canceling headphones and a Blue Yeti USB microphone with multiple modes. Some also use a pop filter or foam windscreen as well. This makes my audio clear, podcast quality. I also use a stand-up desk that converts my traditional desk to a standing workstation.</p>
<p>I think some of the biggest advantages are:<br>
- Results-oriented focus on getting things done<br>
- Establishing boundaries between work and life, sticking a schedule that fits your life<br>
- Learning to be intentional about connecting with co-workers by initiating dialog other than task-oriented conversations<br>
- Taking ownership of your health, getting up and walking, using a standup desk<br>
- Being present both at work and at home, engaging community both online and where I live<br>
- Ad-hoc quality time with loved ones<br>
- Embracing asynchronous converstation, and prioritizing tasks<br>
- Becoming a great manager of one, autonomy<br>
- Being open and honest about effort and results<br>
- Learning to empathize with others' situations and think the best about them</p>
<p>In summary, remote work is not about isolation. It is a lifestyle that prioritizes creating a great experience for everyone in your life. My opinion is that we are wired to connect with other people. Sometimes I see a face in the oddest things, especially in the design of cars. By working remotely you have the priviledge to design your community. Spending less time commuting means the freedom to accomplish more things that matter to you.</p>
<p><strong>Highlights:</strong></p>
<p>Working from the beach</p>
<p><img src="proxy.php?url=/media/working-remote-socal.jpg" alt="Working at the beach"></p>
<p>Afternoon walk</p>
<p><img src="proxy.php?url=/media/afternoon-hike.jpg" alt="Afternoon walk"></p>
EmberJS2018 – A Few Suggestionshttps://pixelhandler.dev/posts/emberjs2018-a-few-suggestions2018-05-31T00:00:00+00:002018-05-31T00:00:00+00:00Billy HeatonAt EmberConf 2018 I finally participated in the mentorship program. Back in the early days, I remember how the community was small and it was easy to connect with the developers involved in working...<h2>Community</h2>
<p>At EmberConf 2018 I finally participated in the mentorship program. Back in the early days, I remember how the community was small and it was easy to connect with the developers involved in working on the framework itself. As time has passed, some faces have changed. However, the community has done a great job of reproducing it’s culture of shared opinions and solutions; and has retained it's values of kindness and openness. At the mentorship program event, I saw how this happens. It amazed me as I saw new members of the community show the same camaraderie – it’s contagious!</p>
<p>So for 2018, though Ember makes us productive and happy, it can be easy to pass up participation at local meetups or to become less active on the chat and forums. My hope is that we will continue to hand off the baton of the community values to developers who are new to Ember.</p>
<h2>Marketing</h2>
<p>I hope developers building with Ember can make it a point to share <em>why</em> they enjoy building with Ember. Instead of sharing only how and what we build. We can tell the story of <em>why</em> we build with shared opinions and solutions.</p>
<p>I find that Ember makes me productive and happy. The tools for testing have grown better and better over the years; and the Ember ecosystem proves to help me develop quality and stable software with JavaScript. These shared solutions help me to find common solutions and patterns which have already found success. For example, addons like ember-concurrency and ember-cli-typescript push me to use patterns that others understand, are documented well, and work great. This has been common place in the Ember community for years.</p>
<p>I can keep up with current releases or stick with an LTS version for a time if I need to.</p>
<p>To summarize my message, “Ember in 2018 is not the Ember I remember.” The applications I work on continue to evolve as tooling and shared solutions mature. I can avoid bespoke code yet with addons I can still experiment with new ideas and solutions. I’ve enjoyed implementing features with a unilateral and immutable data flow. I really enjoy being an early adopter of building with TypeScript in Ember applications; as well as using (experimental) decorators. Using classes, decorators, and TypeScript in the applications I develop looks very different from the applications I worked on in 2017. They do feel very modern. At the same time, these tools make it easier for developers, who are less familiar with Ember, to understand the application code.</p>
<h2>Standards</h2>
<p>I hope that Custom Elements are first class in Ember, and that Ember components can upgrade a custom element just adding template bindings. It will be amazing when Components as just baked on the Web Components standards.<br>
- <a href="proxy.php?url=https://html.spec.whatwg.org/multipage/custom-elements.html#custom-elements" target="_blank" rel="noopener noreferrer">Custom Elements</a></p>
<p>Regaring actions, I know they work fine and are documented well. But, I find as new developers begin working on Ember applications actions are confusing. It seems that "closure actions" (using the <code>action</code> keyword in templates) is a goto solution. And, we often pass actions down (since they can talk back). The idea of "data down actions up" has evolved to "data down plus actions down too". I wonder how we can simplify this concept of handing events? I think that perhaps sticking closer to standards may help. I wonder, are we doing too much with actions when we should be authoring events? E.g. using built in events, plus custom events.</p>
<h2>Bugs</h2>
<p>I hope more developers can help triage Ember issues. Helping the core teams find a better signal to noise ratio from the Ember issue tracker on github will go a long way with seeing more bugs closed out. It takes a village. Simply looking over issues and helping developers reproduce their issue with an working example or even creating a failing test would be awesome.</p>
<h2>Documentation</h2>
<p>Ember leaves the model layer up to the needs of the problem domain which developers strive to provide their solution for. Ember data is a great example of how to leverage an ORM and to work with a common API standard, i.e. <em>JSON API</em>. And it provides solutions for custom adapters and serializers to fit just about any need. The abstraction is good. However, I have worked on various applications where the data layer did not fit the solutions Ember data provides. I would like to see more solutions as official documentation that use the Fetch API, or a simple XHR solution. Perhaps developers should be encouraged to follow the data patterns in the domain which their applications live in - versus conforming to an abstraction to an ORM solution. For example, using eventual consistency via a CQRS pattern (for persistence) is very different. What about real time data? How about examples using web sockets?</p>
<p>Also, I’d like to see more examples of using domain/business logic in a UI model layer with components which are simple in scope - the components just leverage data and methods of models (instead of implementing domain specific login within components). I think unit test for models run fast - as they do not need to render anything. And for component tests, domain logic can be stubbed in rendering tests for testing component behaviors.</p>
<p>One thing I find, is that business logic can be sprinkled all over an application - a bit in the component, a bit in the route, a bit in the controller, a bit in the model, and a bunch more in the template(s). I think that the Ember framework provides patterns for developing single page apps which are great. Perhaps, we also need some encouragement as a community to model solutions thoroughly and utilize framework objects to connect to our business logic - that is through our UI model objects vs. complex components and service combinations.</p>
<p>I get that Ember provides Service and Component objects which can be leaned on heavily; as well as a default solution for data models (persistence). These are great tools for building applications on.</p>
<h2>Ember.Object</h2>
<p>Can we just use <code>class</code>, I do. The ember-cli-typescript addon encourages the use of classes and extending <code>EmberObject</code>. I think current appliation code should utilize class syntax vs. <code>EmberObject.exend()</code>.</p>
<h2>A11y</h2>
<p>Good solutions exist for accessibility (A11y) provided through addons. As a commnity I hope we can leverage these and share our success stories more.</p>
<h2>Routing</h2>
<p>There are lots of bugs in the issue tracker for routing. I know Ember has gone though various router implementations. But, what if the router was an addon? And, a simple alternate option existed as well? Perhaps a router that just has a function to match the request URI and a function to render. Maybe ember-simple-router?</p>
<h2>Code splitting</h2>
<p>I am sure everyone is like yeah is there a way to break up the large assets and load modules on demand? Is that solution the ‘engines’ solution? Well, I imagine someone is working on this and I look forward to using it one day :)</p>
<h2>Wrap up</h2>
<p>I am so happy be be a part of a JavaScript community which advances standards (e.g. JS modules) and tooling - while keeping me productive and happy. I like that there is room to experiment as well. And, since the framework evolves by learning from the success of other libraries and tools our applications do not grow stale; we can avoid becoming legacy developers (even though that is a sign of some success, maintaining a legacy application that is). Some things I’m interested in, but have not explored with Ember, are using tools like RxJS or other reactive patterns.</p>
Change, everything will be finehttps://pixelhandler.dev/posts/change-everything-will-be-fine2017-09-04T00:00:00+00:002017-09-04T00:00:00+00:00Billy HeatonLet’s be honest 2016 was a tough year for everyone. Along with that craziness, I gave my job to a friend and began a journey of consulting and eventually back to full time employment. I valued my f...<p>Let’s be honest 2016 was a tough year for everyone. Along with that craziness, I gave my job to a friend and began a journey of consulting and eventually back to full time employment. I valued my former position and looked forward to growing my leadership skills in a software developer manager role. Well that all changed. While consulting, I looked forward to using my experience as a positive influence to benefit the clients which I had the privilege to serve. And eventually, I accepted a position for a company in Texas as a developer. Exercising leadership would look differently now. I’ve worked for companies in Los Angeles, San Francisco, San Diego, Minneapolis, North Carolina. And I’ve worked with people around the globe. The people in Texas have been kind to me. In response to Hurricane Harvey, I’ve witnessed the level of support Texans naturally give to their fellow Texans, a few co-workers on my team spent their Labor Day weekend helping hurricane victims. Cheers Texas, you know how to take care of others better than most. I think I landed well; that makes me happy. A software company in Texas does behave differently than other software companies I’ve worked for. And it’s good, I’d say “even better” (just learned about that phase this year).</p>
<p>Previously in 2016, I lived in a huge house, about 3,700 sq. ft., had a great home office, and had hopes to buy that house. Oops, that didn’t work out, we down sized and moved out of town. (It even took about a year to get back a $4K deposit.) I still work remotely, but now share my office space with another room. I moved to another community in Southern California, I’ve found many kind people, fresh air (yes in CA), and I can see the stars every night. (Not so much before.) Today, friends from our church helped us pull weeds and clean our back yard - on a holiday too, so kind. I miss my big house and the community around my old place, there were kind people there too. I have a big yard and am so happy to see my family work so hard together to prepare for an upcoming back yard party we’re planning. We do not have as much room inside anymore. A friend offered to lend us a tent, which really will make the yard a nice atmosphere for our party. We’ve made some new friends and have kept some old ones too.</p>
<p>I’ve been a Mac user for a very long time, I previously worked for the company that produced those Mac vs PC advertisements that were so funny. I also previously worked for the company that provided ecommerce solutions to the company whose operating system has achieved world domination, and was like the only developer with a mac officially provided by the company, back then PCs were standard issue to developers. I worked on the online store and try/buy solutions to buy Office for Mac. In school, I learned to type on an Apple II (my only personal computer was a Commodore 64). I’ve bought a ton of macs for myself and my family over the past 18 years. And during that time, I only purchased one PC. At one of my old jobs we only supported the Safari web browser; which of course comes on a Mac. So, at my new job I asked for a Mac. Everyone on the team had one, there was one caveat - we run services using Windows based technologies. Also in late 2016, I was issued on of those new Macbook Pro computers with the touch bar (surprise, I really did not like that computer). Then another one, what else would I use? I got to thinking, we run Parallels for the Windows based web services we use. Why not just develop on a Windows machine, I remember I used to before, but it has been almost 10 years. Then I was often the developer who insisted on using a Mac, and now I’m the guy in a large crowd of developers and their Macbook Pros why askes for a PC. So now, I use a Dell laptop at work and a Lenovo laptop at home. Windows is different now, I’ve pleasantly surprised – I’ve been away for some time. And with the Creators Update the Windows Subsystem for Linux works pretty well. I keep telling myself I’ll just run Linux only one day, well for now I step into Ubuntu from Windows 10 using the <code>bash</code> command. Also, I even use a touch screen on my PC. What, the whole screen is a pointing device? Heh, I kind of like that.</p>
<p>The team I work with builds with ASP.Net Core technologies. So, Visual Studio or VS Code is the go to IDE/Editor. My favorite text editor, up to now, was Vim. I still like Vim, but VS Code is really good. I used to laugh at TypeScript; it seemed to me that TypeScript was a misfit within JavaScript; since dynamic types do work. Now I think… types are great, can I build with TypeScript? I recently worked on a feature and chose to use immutable data (in a JavaScript application). What could be wrong with state in a thick client (JavaScript) application? I like the immutable data flow much better now. Thanks to Elm for catching my interest in using types, functional programming, and immutable data. For almost 5 years I have built applications with the Ember.js framework which itself has been through many changes. And now I’m so excited about Web Components, the v1 specification and polyfills are coming along great. Maybe soon I’ll develop mostly with Web Components. I miss some aspects of Rails, Django but look forward to learning the concepts around Domain Driven Design (DDD). The team I’m on, favors many of the concepts and solutions which stem from the DDD methodologies. We value capturing the behaviors within a software system and the ubiquitous language among stake holders/clients than we value how a technology system typically functions. I’ve come to recognize that so many CRUD based solutions were short sighted. I’m interested in RxJS, Cycle.js, and I think I will tinker with these reactive and functional approaches to software development. What has not changed is that I still write a bunch of tests, and keep advocating the importance of writing automated tests, both for developers and users alike.</p>
<p>For the past few years, my wife was a conservator for her grandfather, he suffered from Alzheimer’s. He recently passed. It was a privilege to care for him and know him during his final years. He would say, “you all must be my kin, you are so kind to me”. We’d have a partly and he’d have a surprise more than once during the event. He would become lost and alone, then I’d walk him around the room and re-introduce him to his family, and he was suddenly so happy to be surrounded by people who love him. We will miss him; the responsibility was significant, now that also has changed.</p>
<p>We keep a mini zoo at home, but for the past 18 year we have not had one cat that become our pet, ever. This summer, we adopted a kitty. He is so cute, curious, and cuddly. A few months back we lost the best dog we ever owned, she was incredible. There are five of us at home (in 2016 we launched 2) we came up with five names for the cat, I just call him “Kitty”. I know he will grow up, maybe I’ll call him “Gretsky” then; he likes to play with lids like they are hockey pucks.</p>
<p>In the last year what changed? The political landscape for one. The “twitterverse” seems very polarized and promotes echo chambers. I changed… jobs, roles, computers, operating systems, backend solutions (programming languages), text editor of choice, work for a Southern organization vs Costal, share office space vs personal office, frequency of travel, lost a pet, tried a kind new pet, lost a team, gained a team, downsized our home (in half), launched two children, training (Krav Maga), communities, cities, churches, schools, neighbors, friends, family, responsibilities, health, and eyesight. I am sure I forgot something that belongs on this list. That that is enough. I have not changed much, everything around me changed drastically - I still think different and am one of the crazy ones who thinks they can change the world. Two constants have been with me this past year: change and kindness. Even though I had a tough year in 2016, 2017 has been much better. Don’t get me wrong I get serious threats to my safety and security; and, I do notice the anger around the USA. But, I choose my comfort zone within the storms of life - kindness works well. In fact, kindness is battle tested with withstands change. No wonder when I look back at all the changes that I experienced this past year (and over my career)… I found kindness within the chaos.</p>
<p>I typically blog about software development for the web. The title of this posts may appear off-topic. But I’d like to challenge your perception, would you challenge your own perception? I do, I find it a practice that helps me transition though change. Change happens. It’s your ability to navigate change and show your resilience though it - which makes a difference in your personal and professional life. (These days, these two cross-over anyway). So regardless of the environment you live/work in, regardless of the group think echoing from your social(media) circles - think about your course of action and resolve. Will you be the thermometer who simply reads the temperature; or will you be the thermostat that influences the temperature? My story from ’16 thru ’17 is not unique, change is a dominant force and is inescapable.</p>
<p>In my mind, this post addresses a key principle that is important to software craftsmanship: the ability to embrace change (to extend and receive kindness through it) will help us all build solutions that are not mediocre. Attitude reflects leadership, be the leader and make room for kindness as a constant, aside ongoing change. Building good software includes dealing with people as much as it is does computers, your resolve will result in work that is mediocre or resilient. Be the change that results in delivering even better software solutions.</p>
Working Remote, Somewhere Between Bliss & Distresshttps://pixelhandler.dev/posts/working-remote-somewhere-between-bliss-and-distress2016-09-27T00:00:00+00:002016-09-27T00:00:00+00:00Billy HeatonBefore I get into the details working somewhere between bliss and distress. it’s worth mentioning some motivations for working with a remote developer. Some markets are saturated and it’s difficult...<p>Before I get into the details working somewhere between bliss and distress. it’s worth mentioning some motivations for working with a remote developer. Some markets are saturated and it’s difficult to find local talent. At times, teams are seeking to hire senior talent and looking for ways to make it more attractive for developers to make change. In some cases, a project is short term; and hiring a contractor or consultant is the best way to help a project take flight (we’ll build the airplane in the air). Other times, its more strategic, why invest capital in brick and mortar, in headquarters, when you can put cash into infrastructure and teams? For developers, the motivation may be to make up for lost time and stay closer to home; or, to make time for travel. There are a wide range of motivations why companies and their employees decide to work remotely.</p>
<p>I’ll start with a tale of… when it fails. Simply put… no one at the company is really convinced that they will benefit from the learning it takes to work with a remote team member. Or a remote developer has no real interest in being a team player. It’s more of a compromise… “This is a talented person, how could we pass him/her up?” Or, “We can figure out how to work with a remote employee. How hard can it be?” Alternatively, “We already work with contractors in this or that location, and that works fine.” It starts like so… “We’d like to extend you an offer that I think you can’t refuse, we are happy to work with you remotely and although we don’t have any employees who work 100% remote we’ve had success working with contractors. We can build on that.” Here is the error, an employee is not a contractor, there isn’t a fixed date as a finish line, nor is there motivation from the business to gain an expected outcome like there is with a consultancy. If the business thinks it’s ok to treat the remote employee like an offshore developer, it just won’t last. Not only will it fail fast, the work may not even matter. I worked at one company and for the first 3 months another developer basically threw away all the code I contributed toward browser compatibility (for their beta product). What a waste of effort and resources. In this scenario the business does not posses conviction about the value they gain from learning how to work with a distributed team, it’s more of an experiment. There’s no real skin in the game. When you recognize this, it’s time to make a change, do it gracefully and swiftly. For this (solo) remote developer on a team, the experience is dominantly characterized by distress. In this scenario, there is nothing healthy for either the business nor the “second class” employee, known as the remote developer on a team of in-house developers.</p>
<p>Please note that there is a huge difference between “working from home” as a privilege and “working remote” as a lifestyle. Because a team has a flexible schedule that does not mean it can maintain success with a distributed team. If one team member is distributed everyone needs to know how to collaborate or the work suffers. In-house team members blame the remote developer and don’t include the remote team member in discussions about the project, or design of the product, etc. Having a remote employee means finding a way to make that employee a “first class” employee vs. a “second class” one. The result of treating remote employees as first class is that the organization benefits from the investment in learning how to work with one or more remote team members.</p>
<p>There is another scenario when… it half works. Some in the company like the benefit of having remote workers; however, they see remote work as a privilege. Based on this perception they reserve some aspects of working together for only during in-person exchanges. This is closer to working with a flexible schedule than to removing remotely as a lifestyle. The business has made a compromise; for what ever reason, maybe desperation. It is normal to plan for remote workers to have some face time with the team. But in this case, the business sees work from home as a privilege and incapacitates potential learning and advantages gained by working with a distributed team. Many conversations are saved for in-person exchanges instead of embracing successful practices of distributed teams, e.g. asynchronous communications. The in-house staff find ways to insist on synchronous communications. Since that can rarely happen, communications are extremely limited. The result is mismatched expectations and failure to work effectively toward common goals. Working remotely requires solid communication on both ends; and partial effort, does not lead to success, you get out what you put in. Sooner or later in this scenario, the remote employee gets burned, or becomes the scapegoat.</p>
<p>A third, and somewhat more honest approach is …trying to learn how to add remote workers to a local team. This happens when… some at the company realize there are benefits from learning how to change their current work patterns by the addition of remote workers. It is ok to not to have it all figured out – both onsite and offsite parties understand that the rewards are deferred – until there is a change that takes place, which makes it possible to reap the benefits from a new way of working together. This is still somewhat experimental; but for all parties, it affords growth and discovery of new patterns for success. The circumstances around when to begin this experiment may not matter as much as having skin in the game. Both employee and employer gain from this investment of time and labor toward expanding a team beyond their current geographic limitations. By hiring a remote employee who has experience in both succeeding and failing when growing a distributed team, the company can side-step some pitfalls of figuring this out for the first time. The in-house team and collaborators will need to learn new habits; and find ways to include others that are remote. Open communications and making time for in-person team events, e.g. a week in the office each quarter, a few lunch events or off-site activities can help provide course correction along the way. As long as everyone stays engaged in this effort, eventually the remote employee is just a normal employee who only comes in on occasion for social or strategic meetings.</p>
<p>The final scenario is… when it just works. This can be blissful for remote workers and company in-house staff as well. Many at the company have confidence in the reaping the benefits from working with remote workers. Stakeholders, in-house, and remote team members (whether located primarily on- or off-site) all have found a way to operate and communicate effectively and successfully. Location is not a factor which limits communication and collaboration. Teamwork is typically successful when someone in upper management / or in a position of leadership and influence fully embraces the practices that support working with a distributed team. When planing meetings, there is always an online meeting option, and call-in phone number as a back up plan. Teams use online tools to collaborate. One quote is, “if it didn’t happen online, it didn’t happen”. Artifacts necessary to communicate scope of work or specifications for features (or systems) are readily sharable using online tools. Communications are most often asynchronous, The chat rooms are used by both on- and off-site team members. Everyone is included in conversions and when not possible, those who were out of the loop are filled in with the necessary details to be successful within their team. This does not happen by accident, but instead by intention.</p>
<p>The choice between working with remote vs. in-house developers is not a business decision that makes or breaks a company. Though it does seem to be a question many ask, should we consider hiring remote developers? I would say that is not the right question at all. Business decisions that impact how well people succeed at working together or how quickly they fail at working together are comprised of a wider set of choices regarding how the business interfaces with it’s workforce. What do we gain from learning how to communicate and ship features for our product while working as a distributed team? That, is a much better question to ask.</p>
<p>Once a team embraces a distributed workforce, fully or partially remote with some or none on-site, there are valuable lessons to learn. Communication is important, it takes as least two. Foster communication and open collaboration; succeed and fail openly. Asynchronous communication is the way to encourage collaboration. Interruption is not. There is a time and place for synchronous communications; but that should be limited to when it’s altogether necessary and agreed upon by all parties. A good VOIP phone; or a plain old cell phone with a good service plan works fine. Slack has a decent call feature. Not every meeting needs to include video (or face-to-face) interactions. Just make sure you keep communication open and timely. Choose your tools for process and collaboration wisely; the tools should not be the subject of your pain points. For online meetings have a backup plan, perhaps the primary tool will be an online meeting and the backup may be a conference (call-in) phone number. Decide this in advance of the meeting start time. Chat apps work great; and help teams connect and collaborate. Reach out for help; pair program with team members. When working remotely, avoid isolation. Explore the surrounding areas, or travel. Rent a shared work space as needed. Keep yourself motivated. When working from home, be careful to plan your work hours and plan your life away from work too. Choose and communicate working hours. Draw a boundary for yourself between life and work. Be an effective communicator and set reasonable expectations. Avoid distractions, find a time management system that works for you, perhaps use the Pomodoro Technique®. Focus and flow facilitate great work, protect that. Show up and dress for work, wherever that is. Choosing where to work affords who has the privilege of inturruption. Working at home can mean that loved ones are close by; don’t shut them out, agree on a time to connect. And give them some hugs too.</p>
<p>One final suggestion is… know your limits. Sometimes an experiment fails, find a way to make lemonade from the pile of lemons you were sold. If working as a remote employee isn’t working, try contracting. If working as a fully distributed team is going bonkers, rent an office space and move toward flexible hours. Learn not only from your own mistakes but also from the mistakes of others. Perhaps hire contractors instead of employees when you decide to expand your team beyond the walls of your office. A consultant, worth her/his rate, will make the team they work with shine; by transferring her/his knowledge and experience as each opportunity presents itself. If you fail – don’t give up, try something new.</p>
Managing Developer Happinesshttps://pixelhandler.dev/posts/managing-developer-happiness2016-09-08T00:00:00+00:002016-09-08T00:00:00+00:00Billy HeatonTo manage developers you need to find them first. What attracts quality people and talent worth giving your best for?<p><strong>To manage developers you need to find them first. What attracts quality people and talent worth giving your best for?</strong></p>
<p>That is a loaded question, it depends on the individual. Typically I hear “I want to work with smart people.” I do to. The reality is, as managers we need to surround ourselves with people we can sharpen and who sharpen us. Be honest about your team’s skill and what a values you expect a candidate to bring to your team. Developer’s values vary: flexible schedules, autonomy, working on ambitious products, modern technology, low technical debt, etc. Just ask, it’s ok to have a variety of personalities who team up to build great software, it’s about respecting one another’s values.</p>
<p><strong>Now that you have a team, how do you keep them engaged, motivated and interested in working with your organization?</strong></p>
<p>What keeps their interest? Is it the technology stack? Or, work-life balance? How about owning a piece of the business? Contributing to open source projects? Twenty percent time? Training opportunities? It varies, do your best to meet individual needs and provide a path for growth that fits the expectations of your team. Don’t shut down dreams by refusing to make goals a reality. Go to bat for your team, get them what they need - so that they can focus on work that matters.</p>
<p><strong>As a manager, are you meeting the expectations of your development team?</strong></p>
<p>What do they expect from you? Trust. No micro-managing. A good manager lets the rubbish roll up instead of flow down. Move obstacles and barriers that prevent your team from focusing on their best effort. Flexibility. Support for work-life balance that - Just Works™. Create time to focus on objectives, avoid meetings and keep communication channels open. Provide an opportunity to learn, enjoy life, refresh and recharge, to go to conferences and support local software user groups. Communications should have a frequency that works for the individual. Don’t over-do or under-do what matters to your team. I call this servant leadership. My objective as a manager is to provide the necessary support, whatever it may be, so that my team can shine and take pride in the product of their investment of time, thought, labor and sweat – ultimately shipping valuable software products for users to enjoy.</p>
<p><strong>Do you know what developers expect from work, from the business?</strong></p>
<p>How about a challenge to tackle? A finish line to cross. People to collaborate with, an excellent project manager or business owner. A roadmap that inspires great work. And of course, time away from work! “All work and no play makes Jack a dull boy” –Jack, The Shining. Maybe, work from home. For some, work at work; away from home. For others, a flexible schedule that affords putting family first. Many developers would really like to work for a remote friendly organization. Work is for work and quitting time is for play. It’s fine to mix it up a bit; but remember the company is not your family (aside from a family business that is).</p>
<p><strong>So you’re a manager of developer happiness, what should you do?</strong></p>
<p>Act as a servant, a leader, help your team do their best work. Lend a hand when you are asked, get out of the way when you recognize you're not helping anything. Ask good questions about code, be involved in peer reviews. Set standards and practices that reflect the values of your team and organization’s needs. Put people first, before policies/protocols. Filter out the bull, run the interference your team needs so they can focus on development. Remind your team to go home; the work will be there tomorrow. Question your assumptions and the assumptions of others – in a positive way. Ask about what is best for the team, the company, and the individual. At times all three are not in harmony, find a workable compromise. Take responsibility. If necessary deploy, do the QA work, write CLI tools, write documentation, release notes or create diagrams for systems information. Be a coach, show stakeholders how to work with a development team, how to motivate them. Communicate the value your team brings to the business. Have an opinion, and welcome the opinions of others. Be who you are and respect others for being who they are. When push comes to shove, fight on behalf of your team. Show up and flip tables when it’s the right thing to do – when the benefit outweighs the cost, for everyone. You are the leader, act like it. Have a life outside work, but make work fun too. And, have a sense of humor, you will need it. Expect great days and horrible days – both will pass, keep a steady and positive attitude about a plan forward, regardless.</p>
<p><strong>What about process?</strong></p>
<p>How will your team succeed at getting things done? Do only what works! Experiment and learn along the way. Tools and process should not get in the way, choose wisely. Provide guidelines on how and what your team should prefer, based on team values. For example, a code standards document or style guide helps establish a bar you can raise when needed. Layer work that is dependent, for example only two of these three will likely succeed when in flight concurrently: 1) UI/UX design 2) API contracts 3) Feature development. Working on two of these at a time works to move toward a solution but all three in flight together invites chaos and likely failure to meet goals. Favor process that has proven to be successful elsewhere. Don’t re-invent process for the sake of bespoke ideas. To codify how your team will focus on what is important… adopt successful process that can blend with the footprint of your organization. Pick naming conventions that can work; conventions do not have to be perfect. Perhaps use “git flow” or simply borrow the parts that work for your group. Use Scrum / Agile practices that people recognize, so that you do not have to explain “how“ your team get things done. In order for your team to deliver results that matter, surface actionable work - which brings value when you ship it. And, avoid working on features that are not well defined. Define done.</p>
<p><strong>What about the size of your team?</strong></p>
<p>Prefer a small team, or many small teams if you work in a large organization. This affords leading by example, so you can still write quality code as a manager. It also limits collisions of code and incongruent methodologies from hijacking the work. Align strengths, front-end, API, style wizardry, software design and automated testing chops too. TDD that stuff together or plan a perfect system - various strengths can sharpen the team. Let people do what they do best.</p>
<p><strong>There are some things a manager should avoid.</strong></p>
<p>Only do what you can succeed at, based on your resources. Don’t become the project manager or product owner, focus on developing successful and shippable solutions. “Real artists ship” -Jobs. Don’t get mixed up in company politics, delivering results carries weight and should be the leverage you need when you need to play your cards. Don’t work on the stories that challenge your team, let them enjoy a challenge and come up with a win. Don’t go dark, embrace interruption, help move obstacles for your team. Avoid saying “yes” to anything that is not already groomed, actionable and committed to work on. No means no. An untimely yes means no to what you already said yes to. It takes a lot of “no” to get the best “yes”. Avoid being selfish, we all can be. The team has to succeed, period. Don’t be a jerk, people have feelings, empathy goes a long way. Don’t cave in to pressure for late hours and work through the weekend. You set the example, work smart, not hard and long. Which only results in starting more fires and a fosters a culture that rewards and recognizes people for putting out fires - instead of a culture which avoids starting fires and which focuses on adding value to your product or business. Long/extra hours are a distraction that drips poison into quality software.</p>
<p><strong>You’re a manger of developer happiness, how does a manager sharpen oneself?</strong></p>
<p>Surround yourself with other leaders, at your company or in the software community, both if you’re so fortunate. Take time to have conversations with other leaders. Talk about what makes you tick and listen to the experience of other leaders. Seek mentorship in all areas of you’re life, they seem to blend a bit anyway. Becoming a better leader at home leaks into being a better leader at work and vice-versa. Be helpful where you can, others will recognize that and return the favor, “pay it forward”. Treat others as you would like to be treated, it takes practice. Reflect the best character traits that you’ve seen modeled by mentors or your former managers. Volunteer to get involved with a local software user group, or online development community. Solve other people’s bugs, give back where you can.</p>
<p><strong>Don’t miss this… what must a manger get right?</strong></p>
<p>Say “Thank you.” It matters, people deserve a kind word, especially when they give their best. Recognize valuable contributions. And, have a plan forward, when the tech debt is high, roadmap is steep, the backlog is thick, or you have no clue what’s next. It takes a plan to maintain forward momentum. Maintain and display a positive attitude, “Attitude reflects leadership” (Remember the Titans). Listen. When in doubt, listen some more. Celebrate finishing well. Learn from the past, success and failure both provide valuable lessons. Succeed and fail transparently. Balance work and life commitments for everyone on your team, including yourself. Help others give their best effort, “Yes you can” motivates others, tell yourself that too. Challenge others to grow, provide specific and reachable goals. Do the hard things, you’ll recognize them when they come - and they will come. Don’t be a coward. Set the temperature for your team, like a thermostat, not a thermometer only reading a value. Respond to change instead of reacting to it. Be a leader both at work and outside the workplace, it takes effort. Elevate others, provide a blueprint for growth and provide the experience that is valuable anywhere, and also provide a reason for your teammates to stick around.</p>
<p>There are many stories I could tell of how good managers have taken time to coach me, they have invested their experience and knowledge in me; and helped me to become better at my craft. They stepped in when I was down and propped me up toward success - by believing in me and challenging me. Mentors and business coaches have challenged me personally to be my best. I still tell myself, “you can do better,” because the race is not over yet. Also, I’ve worked with some managers who were so obnoxious I could just puke. I learned to move forward when I must. If all fails, be a manager of one, and start over again. Ahead, there are battles to win, competitors to face, and most importantly a team at your side - to make work and life more interesting. Do what you can for the people who look to you for leadership and remain teachable, surround yourself and your team with the best people you can mobilize, and build great software. Users everywhere are depending on it, they need it - we all do.</p>
Developing Ember Addons: Next Generation CSS with PostCSShttps://pixelhandler.dev/posts/developing-ember-addons-next-generation-css-with-postcss2016-02-24T00:00:00+00:002016-02-24T00:00:00+00:00Billy HeatonMy Goals:<p>My Goals:</p>
<p>A) In an (Ember CLI) addon - add PostCSS processing to my (Ember CLI) application's build (and utilize a few selected PostCSS plugins for conversion of upcoming CSS syntax for today's browsers). I am digging PostCSS! I'm very satisfied using next generation CSS syntax in my applications today.</p>
<p>B) To move common styles out of various applications and into one single Ember CLI addon. (I still want to continue using PostCSS).</p>
<p>I ran into a snag, the Ember CLI addon to process I used did not Just Work™ as I hoped.</p>
<p>I have used Broccoli as a build pipeline for a JavaScript library, So I thought - "How hard could it be?"</p>
<p>I found that it is not trivial. Fortunately, there are other developers who have already crossed this bridge. I found help in the <a href="proxy.php?url=https://embercommunity.slack.com" target="_blank" rel="noopener noreferrer">embercommunity.slack.com</a> chatrooms (#ember-cli and #need-help), <a href="proxy.php?url=https://ember-community-slackin.herokuapp.com" target="_blank" rel="noopener noreferrer">sign up here</a>.</p>
<p>The question of "Where in the addon do I put my styles?" did confused me. I tried using <code>addon/styles/addon.css</code> then tried <code>app/styles/addon.css</code> vs. <code>app/styles/app.css</code>. What surpised my was that when I put styles into <code>addon/my-file.css</code> and processed it with <code>broccoli-postcss</code> (an npm module) the styles were built into the <code>dist/vendor.css</code> file. At first, this didn't make sense to me. My assumption was that files in the addon's <code>app</code> directory would be merged with the files in the consuming application's <code>app</code> directory. However, that was not the case.</p>
<p>Someone in the chat channel, posted a link to an issue for an <code>ember-cli</code> thread that went back a year or so; which included discussion about how styles behave in addons as well as what magic surrounded processing CSS in an Ember CLI addon. So, my first thought was - "avoid the magic"; like I avoid the plague. So, I continued to setup a customized build solution in a new addon (for common styles); and to simply use CSS files within a <code>styles</code> directory in the addon root directory. That worked for me. Well it did seem that I should begin to look for a pattern in existing Ember addons that include CSS styles as the primary solution, like a UI kit.</p>
<p>Apparently, it's a thing to place CSS files in an addon inside the <code>app</code> directory. And by doing so, the CSS files are built into the <code>dist/vendor.css</code> file. Ok, that's how the magic works, nice :) - it follows that the consuming applications can import the addon's <code>vendor</code> styles into the application's own <code>vendor</code> directory.</p>
<p>Perhaps I have a handle on unravelling this "mystery" of where styles go, how styles are built, and how a consuming application imports the addon's stylesheets. (This was not obvious at first.)</p>
<p>I thought "oh, I should build an addon for processing PostCSS, for addon development". Well, the solution was less than 100 lines of code. Maybe it's better to just use this solution as boilerplate code instead of creating yet another addon. (<a href="proxy.php?url=https://www.emberaddons.com" target="_blank" rel="noopener noreferrer">emberaddons.com</a> already has over 2,000 NPM modules.)</p>
<p>I read an article, titled <a href="proxy.php?url=http://www.mikeperham.com/2016/02/09/kill-your-dependencies/" target="_blank" rel="noopener noreferrer">Kill Your Dependencies</a>, which emphasized owning code that you can, exercising caution when selecting a dependency, and advocated responsibility for library developers. At this point, I discouraged myself from writing Yet Another Library™ for processing PostCSS. It's responsible, for now, to simply share my solution and to explain how Ember developers can customize a <a href="proxy.php?url=http://broccolijs.com" target="_blank" rel="noopener noreferrer">Broccoli</a> build pipeline, within an Ember CLI addon, as a solution for processing CSS, perhaps with PostCSS.</p>
<h3>My Use Cases (Wish List)</h3>
<ul>
<li>I want to use an Ember CLI Addon as a library for common styles for various applictions that can share a common set of styles. This addon will become the shared styles for a group of application engines within the platform I work on.</li>
<li>I don't want to publish this addon on NPM, I want to use a URL to the git repository.</li>
<li>It would be great if I could distribute the CSS file and have a common URL to load the styles from, so that the browser caches common URL.</li>
<li>As the applications grow, I want to remove common styles from various applications into this library of common styles.</li>
<li>I like versioned dependencies, even for a CSS library.</li>
<li>I want to use modern CSS today, PostCSS provides that. Perhaps one day, the modern CSS I write today will become standard; then I can remove the custom processing.</li>
<li>I'd like to just install the addon for my stylesheets, and the CSS should Just Work™. I'm ok with owning that.</li>
</ul>
<p>If you're curious about how this solution turned out for me…</p>
<p><strong>Once I understood how Ember CLI addons proccess CSS in the build pipeline it was fairly simple to add custom processing with Broccoli.</strong></p>
<h3>TLDR; see the repositories below:</h3>
<ul>
<li><a href="proxy.php?url=https://gitlab.com/pixelhandler/xyz-styles" target="_blank" rel="noopener noreferrer">https://gitlab.com/pixelhandler/xyz-styles</a></li>
<li><a href="proxy.php?url=https://gitlab.com/pixelhandler/xyz-foundation" target="_blank" rel="noopener noreferrer">https://gitlab.com/pixelhandler/xyz-foundation</a></li>
</ul>
<p>The <code>xyz-styles</code> addon is utilized by the <code>xyz-foundation</code> app. </p>
<h3>PostCSS processing in an Ember CLI Addon</h3>
<p>Below is an explaination the solution I found, starting from the end of the story (the part where it just works).</p>
<p>First an application needs to install the addon (for common styles).</p>
<pre class="highlight"><code>ember install git+ssh://[email protected]:pixelhandler/xyz-styles.git
</code></pre>
<p>This command results in adding dependencies to my package.json. I moved the 'styles addon' to <code>dependencies</code> (from <code>devDependencies</code>):</p>
<ul>
<li><a href="proxy.php?url=https://gitlab.com/pixelhandler/xyz-foundation/blob/master/package.json#L50" target="_blank" rel="noopener noreferrer">https://gitlab.com/pixelhandler/xyz-foundation/blob/master/package.json#L50</a></li>
</ul>
<p>The output was…</p>
<pre class="highlight"><code>version: 2.3.0
Installed packages for tooling via npm.
installing xyz-styles
install packages autoprefixer, broccoli-funnel, broccoli-merge-trees, broccoli-postcss, postcss-cssnext, postcss-import
Installing packages for tooling via npm..caniuse-api: Generation ok
Installed packages for tooling via npm.
Installed addon package.
</code></pre>
<p>In order for this command to work, the addon needed a <a href="proxy.php?url=https://gitlab.com/pixelhandler/xyz-styles/blob/master/blueprints/xyz-styles/index.js" target="_blank" rel="noopener noreferrer">blueprint</a>…</p>
<pre class="highlight"><code><span class="cm">/* jshint node: true */</span>
<span class="cm">/* global module */</span>
<span class="nx">module</span><span class="p">.</span><span class="nx">exports</span> <span class="o">=</span> <span class="p">{</span>
<span class="na">description</span><span class="p">:</span> <span class="dl">'</span><span class="s1">xyz-styles</span><span class="dl">'</span><span class="p">,</span>
<span class="na">normalizeEntityName</span><span class="p">:</span> <span class="nf">function </span><span class="p">()</span> <span class="p">{},</span>
<span class="na">afterInstall</span><span class="p">:</span> <span class="nf">function </span><span class="p">()</span> <span class="p">{</span>
<span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nf">addPackagesToProject</span><span class="p">([</span>
<span class="p">{</span> <span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">autoprefixer</span><span class="dl">'</span><span class="p">,</span> <span class="na">target</span><span class="p">:</span> <span class="dl">'</span><span class="s1">^6.3.3</span><span class="dl">'</span> <span class="p">},</span>
<span class="p">{</span> <span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">broccoli-funnel</span><span class="dl">'</span><span class="p">,</span> <span class="na">target</span><span class="p">:</span> <span class="dl">'</span><span class="s1">^1.0.1</span><span class="dl">'</span> <span class="p">},</span>
<span class="p">{</span> <span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">broccoli-merge-trees</span><span class="dl">'</span><span class="p">,</span><span class="na">target</span><span class="p">:</span> <span class="dl">'</span><span class="s1">^1.1.1</span><span class="dl">'</span> <span class="p">},</span>
<span class="p">{</span> <span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">broccoli-postcss</span><span class="dl">'</span><span class="p">,</span> <span class="na">target</span><span class="p">:</span> <span class="dl">'</span><span class="s1">^2.1.1</span><span class="dl">'</span> <span class="p">},</span>
<span class="p">{</span> <span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">postcss-cssnext</span><span class="dl">'</span><span class="p">,</span> <span class="na">target</span><span class="p">:</span> <span class="dl">'</span><span class="s1">^2.4.0</span><span class="dl">'</span> <span class="p">},</span>
<span class="p">{</span> <span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">postcss-import</span><span class="dl">'</span><span class="p">,</span> <span class="na">target</span><span class="p">:</span> <span class="dl">'</span><span class="s1">^8.0.2</span><span class="dl">'</span> <span class="p">}</span>
<span class="p">]);</span>
<span class="p">}</span>
<span class="p">};</span>
</code></pre>
<p>My <code>afterInstall</code> hook in the <code>index.js</code> file adds required dependencies for processing CSS files using PostCSS and a couple plugins for <code>cssnext</code> and <code>import</code>.</p>
<p>Learn more about Ember CLI's <a href="proxy.php?url=https://github.com/ember-cli/ember-cli/blob/master/ADDON_HOOKS.md" target="_blank" rel="noopener noreferrer">Addon Hooks</a>.</p>
<p>With these NPM modules in place, this "xyz-styles" addon can utilize them to process the CSS which utilize next generation CSS syntax.</p>
<p>An Ember CLI addon uses it's <code>index.js</code> file to provide hooks for the consuming application. The consuming application can process the modern CSS syntax provided within the addon's code though the <code>included</code> hook. This hook in the addon is used by the consuming applicaiton as the entry point to build the stylesheets into the application's <code>vendor.css</code> file. The vendor CSS will be imported into the consuming Ember application's vendor styles.</p>
<p>Below is the <code>xyz-styles/index.js</code> file:</p>
<pre class="highlight"><code><span class="cm">/* jshint node: true */</span>
<span class="cm">/* global require, module */</span>
<span class="dl">'</span><span class="s1">use strict</span><span class="dl">'</span><span class="p">;</span>
<span class="nx">module</span><span class="p">.</span><span class="nx">exports</span> <span class="o">=</span> <span class="p">{</span>
<span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">xyz-styles</span><span class="dl">'</span><span class="p">,</span>
<span class="na">getCssFileName</span><span class="p">:</span> <span class="nf">function </span><span class="p">()</span> <span class="p">{</span>
<span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">name</span> <span class="o">+</span> <span class="dl">'</span><span class="s1">.css</span><span class="dl">'</span><span class="p">;</span>
<span class="p">},</span>
<span class="na">isAddon</span><span class="p">:</span> <span class="nf">function </span><span class="p">()</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">keywords</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">project</span><span class="p">.</span><span class="nx">pkg</span><span class="p">.</span><span class="nx">keywords</span><span class="p">;</span>
<span class="k">return </span><span class="p">(</span><span class="nx">keywords</span> <span class="o">&&</span> <span class="nx">keywords</span><span class="p">.</span><span class="nf">indexOf</span><span class="p">(</span><span class="dl">'</span><span class="s1">ember-addon</span><span class="dl">'</span><span class="p">)</span> <span class="o">!==</span> <span class="o">-</span><span class="mi">1</span><span class="p">);</span>
<span class="p">},</span>
<span class="na">included</span><span class="p">:</span> <span class="nf">function </span><span class="p">(</span><span class="nx">app</span><span class="p">)</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nx">_super</span><span class="p">.</span><span class="nf">included</span><span class="p">(</span><span class="nx">app</span><span class="p">);</span>
<span class="k">if </span><span class="p">(</span><span class="o">!</span><span class="k">this</span><span class="p">.</span><span class="nf">isAddon</span><span class="p">())</span> <span class="p">{</span>
<span class="nx">app</span><span class="p">.</span><span class="k">import</span><span class="p">(</span><span class="dl">'</span><span class="s1">vendor/</span><span class="dl">'</span> <span class="o">+</span> <span class="k">this</span><span class="p">.</span><span class="nf">getCssFileName</span><span class="p">());</span>
<span class="p">}</span>
<span class="p">},</span>
<span class="na">treeForVendor</span><span class="p">:</span> <span class="nf">function </span><span class="p">(</span><span class="nx">node</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if </span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nf">isAddon</span><span class="p">())</span> <span class="p">{</span> <span class="k">return</span> <span class="nx">node</span><span class="p">;</span> <span class="p">}</span>
<span class="kd">var</span> <span class="nx">path</span> <span class="o">=</span> <span class="nf">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">path</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">Funnel</span> <span class="o">=</span> <span class="nf">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">broccoli-funnel</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">mergeTrees</span> <span class="o">=</span> <span class="nf">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">broccoli-merge-trees</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">compileCSS</span> <span class="o">=</span> <span class="nf">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">broccoli-postcss</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">styles</span> <span class="o">=</span> <span class="nx">path</span><span class="p">.</span><span class="nf">join</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">project</span><span class="p">.</span><span class="nx">nodeModulesPath</span><span class="p">,</span> <span class="k">this</span><span class="p">.</span><span class="nx">name</span><span class="p">,</span> <span class="dl">'</span><span class="s1">app</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">styles</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">inputTrees</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Funnel</span><span class="p">(</span><span class="nx">styles</span><span class="p">,</span> <span class="p">{</span> <span class="na">include</span><span class="p">:</span> <span class="p">[</span><span class="sr">/.css$/</span><span class="p">]</span> <span class="p">});</span>
<span class="kd">var</span> <span class="nx">inputFile</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nf">getCssFileName</span><span class="p">();</span>
<span class="kd">var</span> <span class="nx">outputFile</span> <span class="o">=</span> <span class="nx">inputFile</span><span class="p">;</span>
<span class="kd">var</span> <span class="nx">plugins</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nf">getPlugins</span><span class="p">();</span>
<span class="kd">var</span> <span class="nx">sourceMaps</span> <span class="o">=</span> <span class="p">{</span> <span class="na">inline</span><span class="p">:</span> <span class="kc">true</span> <span class="p">};</span>
<span class="kd">var</span> <span class="nx">css</span> <span class="o">=</span> <span class="nf">compileCSS</span><span class="p">([</span><span class="nx">inputTrees</span><span class="p">],</span> <span class="nx">inputFile</span><span class="p">,</span> <span class="nx">outputFile</span><span class="p">,</span> <span class="nx">plugins</span><span class="p">,</span> <span class="nx">sourceMaps</span><span class="p">);</span>
<span class="nx">node</span> <span class="o">=</span> <span class="p">(</span><span class="nx">node</span><span class="p">)</span> <span class="p">?</span> <span class="nf">mergeTrees</span><span class="p">([</span> <span class="nx">node</span><span class="p">,</span> <span class="nx">css</span> <span class="p">])</span> <span class="p">:</span> <span class="nx">css</span><span class="p">;</span>
<span class="k">return</span> <span class="nx">node</span><span class="p">;</span>
<span class="p">},</span>
<span class="nf">getPlugins</span><span class="p">()</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">autoprefixer</span> <span class="o">=</span> <span class="nf">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">autoprefixer</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">cssnext</span> <span class="o">=</span> <span class="nf">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">postcss-cssnext</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">cssimport</span> <span class="o">=</span> <span class="nf">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">postcss-import</span><span class="dl">'</span><span class="p">);</span>
<span class="k">return</span> <span class="p">[</span>
<span class="p">{</span> <span class="na">module</span><span class="p">:</span> <span class="nx">autoprefixer</span><span class="p">,</span> <span class="na">options</span><span class="p">:</span> <span class="p">{</span> <span class="na">browsers</span><span class="p">:</span> <span class="p">[</span><span class="dl">'</span><span class="s1">last 2 version</span><span class="dl">'</span><span class="p">]</span> <span class="p">}</span> <span class="p">},</span>
<span class="p">{</span> <span class="na">module</span><span class="p">:</span> <span class="nx">cssimport</span> <span class="p">},</span>
<span class="p">{</span> <span class="na">module</span><span class="p">:</span> <span class="nx">cssnext</span><span class="p">,</span> <span class="na">options</span><span class="p">:</span> <span class="p">{</span> <span class="na">sourcemap</span><span class="p">:</span> <span class="kc">true</span> <span class="p">}</span> <span class="p">}</span>
<span class="p">];</span>
<span class="p">}</span>
<span class="p">};</span>
</code></pre>
<p>The <code>treeForVendor</code> hook above utilizes the PostCSS modules and a few Broccoli.js modules - to build the CSS with the build pipeline of the consuming application. I used a <code>getPlugins</code> method to setup the options for the <code>broccoli-postcss</code> module to compile the CSS with.</p>
<p>The proccesing by the <code>included</code> hook will only occur in the case where there is a consuming app including the addon. There are two points of interest… when the consuming application includes the addon…</p>
<ol>
<li>using the <code>treeForVendor</code> hook, the addon's <code>app/xyz-styles.css</code> file is processed and written to <code>vendor/xyz-styles.css</code>, and</li>
<li>the processed styles are imported using <code>app.import</code> within the <code>include</code> hook of the addon. That's it.</li>
</ol>
<p>What if I want to build the styles without a consuing app?</p>
<p>In the example repositories, linked above (see TLDR;) - the <code>xyz-foundation</code> application consumes the <code>xyz-styles</code> addon. What if I want to share the processed styles with a older application, that is not using Ember CLI yet? The <code>ember-cli-build.js</code> file is the build recipe for the addon, including its "dummy" (or test) application. This build recipe is not used by a consuming application. So I could use it during development to build a distribution CSS file from this styles (library) addon. Potentially, Ember CLI's build could be used to build generic CSS libraries too, nice :)</p>
<p>Notice that the <a href="proxy.php?url=https://gitlab.com/pixelhandler/xyz-styles/blob/master/ember-cli-build.js" target="_blank" rel="noopener noreferrer">ember-cli-build.js</a> file is very similar to the <code>index.js</code> file…</p>
<pre class="highlight"><code><span class="cm">/*jshint node:true*/</span>
<span class="cm">/* global require, module */</span>
<span class="kd">var</span> <span class="nx">EmberAddon</span> <span class="o">=</span> <span class="nf">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">ember-cli/lib/broccoli/ember-addon</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">autoprefixer</span> <span class="o">=</span> <span class="nf">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">autoprefixer</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">cssnext</span> <span class="o">=</span> <span class="nf">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">postcss-cssnext</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">cssimport</span> <span class="o">=</span> <span class="nf">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">postcss-import</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">Funnel</span> <span class="o">=</span> <span class="nf">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">broccoli-funnel</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">compileCSS</span> <span class="o">=</span> <span class="nf">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">broccoli-postcss</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">path</span> <span class="o">=</span> <span class="nf">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">path</span><span class="dl">'</span><span class="p">);</span>
<span class="nx">module</span><span class="p">.</span><span class="nx">exports</span> <span class="o">=</span> <span class="kd">function</span><span class="p">(</span><span class="nx">defaults</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">options</span> <span class="o">=</span> <span class="p">{</span>
<span class="na">plugins</span><span class="p">:</span> <span class="p">[</span>
<span class="p">{</span> <span class="na">module</span><span class="p">:</span> <span class="nx">autoprefixer</span><span class="p">,</span> <span class="na">options</span><span class="p">:</span> <span class="p">{</span> <span class="na">browsers</span><span class="p">:</span> <span class="p">[</span><span class="dl">'</span><span class="s1">last 2 version</span><span class="dl">'</span><span class="p">]</span> <span class="p">}</span> <span class="p">},</span>
<span class="p">{</span> <span class="na">module</span><span class="p">:</span> <span class="nx">cssimport</span> <span class="p">},</span>
<span class="p">{</span> <span class="na">module</span><span class="p">:</span> <span class="nx">cssnext</span><span class="p">,</span> <span class="na">options</span><span class="p">:</span> <span class="p">{</span> <span class="na">sourcemap</span><span class="p">:</span> <span class="kc">true</span> <span class="p">}</span> <span class="p">}</span>
<span class="p">]</span>
<span class="p">};</span>
<span class="kd">var</span> <span class="nx">app</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">EmberAddon</span><span class="p">(</span><span class="nx">defaults</span><span class="p">,</span> <span class="p">{</span> <span class="na">postcssOptions</span><span class="p">:</span> <span class="nx">options</span> <span class="p">});</span>
<span class="kd">var</span> <span class="nx">file</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">xyz-styles.css</span><span class="dl">'</span><span class="p">;</span>
<span class="kd">var</span> <span class="nx">styles</span> <span class="o">=</span> <span class="nx">path</span><span class="p">.</span><span class="nf">join</span><span class="p">(</span><span class="dl">'</span><span class="s1">./</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">app</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">styles</span><span class="dl">'</span><span class="p">);</span>
<span class="nx">styles</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Funnel</span><span class="p">(</span><span class="nx">styles</span><span class="p">,</span> <span class="p">{</span> <span class="na">include</span><span class="p">:</span> <span class="p">[</span><span class="sr">/.css$/</span><span class="p">]</span> <span class="p">});</span>
<span class="kd">var</span> <span class="nx">css</span> <span class="o">=</span> <span class="nf">compileCSS</span><span class="p">([</span><span class="nx">styles</span><span class="p">],</span> <span class="nx">file</span><span class="p">,</span> <span class="dl">'</span><span class="s1">vendor/</span><span class="dl">'</span> <span class="o">+</span> <span class="nx">file</span><span class="p">,</span> <span class="nx">options</span><span class="p">.</span><span class="nx">plugins</span><span class="p">,</span> <span class="p">{</span> <span class="na">inline</span><span class="p">:</span> <span class="kc">false</span> <span class="p">});</span>
<span class="k">return</span> <span class="nx">app</span><span class="p">.</span><span class="nf">toTree</span><span class="p">([</span><span class="nx">css</span><span class="p">]);</span>
<span class="p">};</span>
</code></pre>
<p>The build recipe above uses the same strategy as the build setup in the addon's index.js file. It utilizes a file <code>xyz-styles.css</code> as the target for input, and outputs the same filename inside the <code>dist/vendor</code> directory.</p>
<h3>Next Generation CSS syntax using PostCSS</h3>
<p>The <code>xyz-styles</code> addon's <code>app/styles</code> directory is setup with a file named <code>xyz-styles.css</code> as the main file to process. The build recipe utilizes a plugin, <code>postcss-import</code> to concatinate the files that are imported. The <code>postcss-cssnext</code> plugin allows the use of modern or next generation CSS syntax, like <code>color: var(--name);</code>.</p>
<p>See these example files:</p>
<ul>
<li><a href="proxy.php?url=https://gitlab.com/pixelhandler/xyz-styles/blob/master/app/styles/xyz-styles.css" target="_blank" rel="noopener noreferrer">app/styles/xyz-styles.css</a> uses the import plugin</li>
<li><a href="proxy.php?url=https://gitlab.com/pixelhandler/xyz-styles/blob/master/app/styles/colors.css" target="_blank" rel="noopener noreferrer">app/styles/colors.css</a> uses the cssnext plugin for variable definition</li>
<li><a href="proxy.php?url=https://gitlab.com/pixelhandler/xyz-styles/blob/master/app/styles/elements.css" target="_blank" rel="noopener noreferrer">app/styles/elements.css</a> and <a href="proxy.php?url=https://gitlab.com/pixelhandler/xyz-styles/blob/master/app/styles/classes.css" target="_blank" rel="noopener noreferrer">app/styles/classes.css</a> assign a CSS variable</li>
</ul>
<p>The consuming application (xyx-foundation) uses a similar file structure as the css addon (xyz-styles). This demonstrates how an Ember CLI application handles the load order for <code>app/styles</code> in both the application and and addon codebases. By default, the styles defined in the <code>app/styles</code> directory of the application, are built into the application's CSS file, <code>xyz-foundation.css</code>. This file is loaded after the CSS imported from the addon(s) - which are built into the <code>vendor.css</code> file. This default order of loading styles in the applicaiton's <code>index.html</code> file ensures that the vendor CSS styles are the base and the applicaiton's CSS styles can redefine or extend those vendor style declarations from withing the applicaiton's own CSS <code>app.css</code> file(s).</p>
<p>In this example, the xyz-foundation app defines a <code>body</code> style with the color as <strong>blue</strong>. And the addon, xyz-styles, also defines the body style but with color as <strong>red</strong>. The foundaiton app defines a CSS class named <code>blue</code> and the styles addon defines a CSS class named <code>red</code>. In the foundation application's template, <a href="proxy.php?url=https://gitlab.com/pixelhandler/xyz-foundation/blob/master/app/templates/application.hbs" target="_blank" rel="noopener noreferrer">app/templates/application.hbs</a>, these classes can be used.</p>
<pre class="highlight"><code><h2 id="title">Welcome to <span class="red">Ember</span></h2>
</code></pre>
<p>The <code>red</code> class above is introduced to the application's vendor CSS by the styles addon. The title text is blue since the vendor CSS set the text color to <strong>red</strong>, but the application's CSS defines the body text as <strong>blue</strong>.</p>
<p><img src="proxy.php?url=/media/xyz-foundation-app.jpg" alt="xyz-foundation-app screenshot"></p>
<p><img src="proxy.php?url=/media/xyz-foundation-css.jpg" alt="xyz-foundation css stylesheet screenshot"></p>
<p><img src="proxy.php?url=/media/xyz-styles-vendor-css.jpg" alt="xyz-foundation vendor styles screenshot"></p>
Ember.js: Handling Failure Using Route `error` Substateshttps://pixelhandler.dev/posts/emberjs-handling-failure-using-route-error-substates2016-01-19T00:00:00+00:002016-01-19T00:00:00+00:00Billy HeatonAt the top level, where all error events bubble to, is the ApplicationRoute.
An applicationerror substate can be added using a combination of both an
application-error route and application-error t...<h2>Practical Example…</h2>
<p>At the top level, where all <code>error</code> events bubble to, is the <code>ApplicationRoute</code>.<br>
An <code>application_error</code> substate can be added using a combination of both an<br>
<code>application-error</code> route and <code>application-error</code> template. If you only display<br>
the <code>error.message</code> and have no need to use a condition to set a title on the<br>
template, then you could use the template alone (without an error route substate).</p>
<p>The application error template below handles any error thrown by a route that is<br>
not handled by a child route's <code>error</code> substate.</p>
<p>The <strong>app/router.js</strong> file will not need any error routes added, they are built-in.</p>
<p>Since the <code>model</code> is passed to the <code>setupController(controller, error)</code> hook,<br>
the <code>error.message</code> property can be rendered to notify the user of the error.</p>
<p><strong>app/templates/application-error.hbs</strong></p>
<pre class="highlight"><code><h1>Oops, the app is borked…</h1>
<p>{{model.message}}</p>
</code></pre>
<p>Alternatively, when a substate is not used to display an error notification,<br>
your application template can display any messages that you set on the<br>
application controller; e.g. <code>errorMessage</code> and <code>errorDetails</code>.</p>
<p><strong>app/templates/application.hbs</strong></p>
<pre class="highlight"><code>{{#if errorMessage}}
<button class="error-message" {{action 'dismissErrorMessage'}}>
{{errorMessage}} {{errorDetails}}
</button>
{{/if}}
{{outlet}}
</code></pre>
<p>If you want to vary the error notification text of the error substate template,<br>
use the <code>setupController</code> hook to set a <code>title</code> property for the template.</p>
<p>In a route (below the application), e.g. the <code>PostRoute</code>, an error substate can<br>
be used to branch the display of the title to differentiate between a <strong>client</strong><br>
and <strong>server</strong> error like so:</p>
<p><strong>app/templates/post-error.hbs</strong></p>
<pre class="highlight"><code><h1>{{title}}</h1>
<p>{{model.message}}</p>
</code></pre>
<p>The <code>title</code> attribute of the controller is used in the above template. It is<br>
customized depending on the error code.</p>
<p><strong>app/routes/post-error.js</strong></p>
<pre class="highlight"><code><span class="k">import</span> <span class="nx">Ember</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">ember</span><span class="dl">'</span><span class="p">;</span>
<span class="k">export</span> <span class="k">default</span> <span class="nx">Ember</span><span class="p">.</span><span class="nx">Route</span><span class="p">.</span><span class="nf">extend</span><span class="p">({</span>
<span class="nf">setupController</span><span class="p">(</span><span class="nx">controller</span><span class="p">,</span> <span class="nx">error</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">let</span> <span class="nx">title</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">Oops, this post is borked…</span><span class="dl">'</span><span class="p">;</span>
<span class="kd">let</span> <span class="nx">code</span> <span class="o">=</span> <span class="nx">error</span><span class="p">.</span><span class="nx">code</span> <span class="o">||</span> <span class="nx">error</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="dl">'</span><span class="s1">code</span><span class="dl">'</span><span class="p">);</span>
<span class="k">if </span><span class="p">(</span><span class="nx">code</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if </span><span class="p">(</span><span class="nx">code</span> <span class="o">>=</span> <span class="mi">500</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">title</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">Oops, there was a server error…</span><span class="dl">'</span><span class="p">;</span>
<span class="p">}</span> <span class="k">else</span> <span class="k">if </span><span class="p">(</span><span class="nx">code</span> <span class="o">===</span> <span class="mi">404</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">title</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">Opps, can't find this one…</span><span class="dl">"</span><span class="p">;</span>
<span class="p">}</span>
<span class="nx">controller</span><span class="p">.</span><span class="nf">set</span><span class="p">(</span><span class="dl">'</span><span class="s1">title</span><span class="dl">'</span><span class="p">,</span> <span class="nx">title</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">this</span><span class="p">.</span><span class="nf">_super</span><span class="p">(</span><span class="nx">controller</span><span class="p">,</span> <span class="nx">error</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">});</span>
</code></pre>
<p>Since the application template may be used for errors that you do not need to<br>
transition to an <code>error</code> substate, the user will need a way to dismiss. An<br>
action <code>dismissErrorMessage</code> can be used to clear application error properties.</p>
<p><strong>app/routes/application.js</strong></p>
<pre class="highlight"><code><span class="k">import</span> <span class="nx">Ember</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">ember</span><span class="dl">'</span><span class="p">;</span>
<span class="k">export</span> <span class="k">default</span> <span class="nx">Ember</span><span class="p">.</span><span class="nx">Route</span><span class="p">.</span><span class="nf">extend</span><span class="p">({</span>
<span class="na">errorMessage</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
<span class="na">errorDetails</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
<span class="na">actions</span><span class="p">:</span> <span class="p">{</span>
<span class="nf">dismissErrorMessage</span><span class="p">()</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nf">controllerFor</span><span class="p">(</span><span class="dl">'</span><span class="s1">application</span><span class="dl">'</span><span class="p">).</span><span class="nf">setProperties</span><span class="p">({</span>
<span class="dl">'</span><span class="s1">errorMessage</span><span class="dl">'</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
<span class="dl">'</span><span class="s1">errorDetails</span><span class="dl">'</span><span class="p">:</span> <span class="kc">null</span>
<span class="p">});</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">});</span>
</code></pre>
<p>The <code>application_error</code> substate will be used to display any <a href="proxy.php?url=http://httpstatus.es/500" target="_blank" rel="noopener noreferrer">500</a> errors, or<br>
a <a href="proxy.php?url=http://httpstatus.es/404" target="_blank" rel="noopener noreferrer">404</a> error. However, in the case of a specific client error like <a href="proxy.php?url=http://httpstatus.es/400" target="_blank" rel="noopener noreferrer">400</a> or <a href="proxy.php?url=http://httpstatus.es/422" target="_blank" rel="noopener noreferrer">422</a><br>
that your application should handle without making a transition, conditions will<br>
need to be added to branch the behavior between using <code>error</code> substates and the<br>
application <code>error</code> template.</p>
<p>If you use any nested routes, for example <strong>admin/edit</strong> and <strong>admin/create</strong>,<br>
you can define specific error substates at that level in the route structure,<br>
(below the application's default error handing).</p>
<p><strong>app/templates/admin/edit-error.hbs</strong></p>
<pre class="highlight"><code><h1>{{title}}</h1>
<p>{{model.message}}</p>
</code></pre>
<p>Notice the template above is the same as the 'post-error.hbs' template. The post<br>
<code>error</code> template handles the display of non-admin errors; the "/admin/" directory<br>
is used for editing and creating resources.</p>
<p>The route hierarchy used in this example is below. When using the <code>error</code> substates<br>
you do not need to add any routes to the <em>router.js</em> file. Error substates are<br>
built into the <a href="proxy.php?url=http://emberjs.com" target="_blank" rel="noopener noreferrer">Ember.js</a> Router.</p>
<p><strong>app/router.js</strong></p>
<pre class="highlight"><code><span class="k">import</span> <span class="nx">Ember</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">ember</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="nx">config</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./config/environment</span><span class="dl">'</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">Router</span> <span class="o">=</span> <span class="nx">Ember</span><span class="p">.</span><span class="nx">Router</span><span class="p">.</span><span class="nf">extend</span><span class="p">({</span>
<span class="na">location</span><span class="p">:</span> <span class="nx">config</span><span class="p">.</span><span class="nx">locationType</span>
<span class="p">});</span>
<span class="nx">Router</span><span class="p">.</span><span class="nf">map</span><span class="p">(</span><span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nf">route</span><span class="p">(</span><span class="dl">'</span><span class="s1">index</span><span class="dl">'</span><span class="p">,</span> <span class="p">{</span> <span class="na">path</span><span class="p">:</span> <span class="dl">'</span><span class="s1">/</span><span class="dl">'</span> <span class="p">});</span>
<span class="k">this</span><span class="p">.</span><span class="nf">route</span><span class="p">(</span><span class="dl">'</span><span class="s1">post</span><span class="dl">'</span><span class="p">,</span> <span class="p">{</span> <span class="na">path</span><span class="p">:</span> <span class="dl">'</span><span class="s1">/:post_id</span><span class="dl">'</span> <span class="p">},</span> <span class="nf">function </span><span class="p">()</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nf">route</span><span class="p">(</span><span class="dl">'</span><span class="s1">detail</span><span class="dl">'</span><span class="p">,</span> <span class="p">{</span> <span class="na">path</span><span class="p">:</span> <span class="dl">'</span><span class="s1">/</span><span class="dl">'</span> <span class="p">});</span>
<span class="k">this</span><span class="p">.</span><span class="nf">route</span><span class="p">(</span><span class="dl">'</span><span class="s1">comments</span><span class="dl">'</span><span class="p">);</span>
<span class="p">});</span>
<span class="k">this</span><span class="p">.</span><span class="nf">route</span><span class="p">(</span><span class="dl">'</span><span class="s1">admin</span><span class="dl">'</span><span class="p">,</span> <span class="nf">function </span><span class="p">()</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nf">route</span><span class="p">(</span><span class="dl">'</span><span class="s1">index</span><span class="dl">'</span><span class="p">);</span>
<span class="k">this</span><span class="p">.</span><span class="nf">route</span><span class="p">(</span><span class="dl">'</span><span class="s1">create</span><span class="dl">'</span><span class="p">);</span>
<span class="k">this</span><span class="p">.</span><span class="nf">route</span><span class="p">(</span><span class="dl">'</span><span class="s1">edit</span><span class="dl">'</span><span class="p">,</span> <span class="p">{</span> <span class="na">path</span><span class="p">:</span> <span class="dl">'</span><span class="s1">:edit_id</span><span class="dl">'</span> <span class="p">});</span>
<span class="p">});</span>
<span class="p">});</span>
<span class="k">export</span> <span class="k">default</span> <span class="nx">Router</span><span class="p">;</span>
</code></pre>
<p>To assit with testing the <code>error</code> substates - I set the <code>PostController</code> of the<br>
backend (API) to respond with an error. This is the repo for the API application:<br>
<a href="proxy.php?url=https://github.com/pixelhandler/blog-api/tree/ember-jsonapi-resources-testing" target="_blank" rel="noopener noreferrer">blog-api</a> using a branch 'ember-jsonapi-resources-testing'. See the commented<br>
code I used to send <a href="proxy.php?url=https://github.com/pixelhandler/blog-api/blob/ember-jsonapi-resources-testing/app/controllers/api/v1/posts_controller.rb#L4-L14" target="_blank" rel="noopener noreferrer">error responses</a>.</p>
<p>I used the <code>setupController</code> hook to set a relevant <code>title</code> for the notification<br>
and used a console <em>warning</em> for a condition that should not be handled by a<br>
transition. In an effort to build a solution for the desired user experience,<br>
it can be helpful to log the error conditions.</p>
<p><strong>app/routes/admin/edit-error.js</strong></p>
<pre class="highlight"><code><span class="k">import</span> <span class="nx">Ember</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">ember</span><span class="dl">'</span><span class="p">;</span>
<span class="k">export</span> <span class="k">default</span> <span class="nx">Ember</span><span class="p">.</span><span class="nx">Route</span><span class="p">.</span><span class="nf">extend</span><span class="p">({</span>
<span class="nf">setupController</span><span class="p">(</span><span class="nx">controller</span><span class="p">,</span> <span class="nx">error</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">let</span> <span class="nx">title</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">Oops, this post is borked…</span><span class="dl">'</span><span class="p">;</span>
<span class="kd">let</span> <span class="nx">code</span> <span class="o">=</span> <span class="nx">error</span><span class="p">.</span><span class="nx">code</span> <span class="o">||</span> <span class="nx">error</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="dl">'</span><span class="s1">code</span><span class="dl">'</span><span class="p">);</span>
<span class="k">if </span><span class="p">(</span><span class="nx">code</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if </span><span class="p">(</span><span class="nx">code</span> <span class="o">>=</span> <span class="mi">500</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">title</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">Oops, there was a server error…</span><span class="dl">'</span><span class="p">;</span>
<span class="p">}</span> <span class="k">else</span> <span class="k">if </span><span class="p">(</span><span class="nx">code</span> <span class="o">===</span> <span class="mi">404</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">title</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">Opps, can't find this one…</span><span class="dl">"</span><span class="p">;</span>
<span class="p">}</span> <span class="k">else</span> <span class="k">if </span><span class="p">(</span><span class="nx">code</span> <span class="o">===</span> <span class="mi">422</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">Ember</span><span class="p">.</span><span class="nx">Logger</span><span class="p">.</span><span class="nf">warn</span><span class="p">(</span><span class="dl">'</span><span class="s1">Not expecting to handle 422 in an error substate</span><span class="dl">'</span><span class="p">);</span>
<span class="p">}</span>
<span class="nx">controller</span><span class="p">.</span><span class="nf">set</span><span class="p">(</span><span class="dl">'</span><span class="s1">title</span><span class="dl">'</span><span class="p">,</span> <span class="nx">title</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">this</span><span class="p">.</span><span class="nf">_super</span><span class="p">(</span><span class="nx">controller</span><span class="p">,</span> <span class="nx">error</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">});</span>
</code></pre>
<p>The post form component sends an <code>update</code> action to persist changes via the Post<br>
resource endpoint.</p>
<p><strong>app/templates/admin/edit.hbs</strong></p>
<pre class="highlight"><code><p><strong>Edit a Blog Post</strong></p>
{{form-post post=model isNew=model.isNew on-edit=(action "update")}}
</code></pre>
<p>The action is triggered after the user exists the field, this prevents a flood<br>
of updates from every keystroke - caused from binding a model property to an input.</p>
<p><strong>app/components/form-post.js</strong></p>
<pre class="highlight"><code><span class="k">import</span> <span class="nx">Ember</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">ember</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="nx">BufferedProxy</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">ember-buffered-proxy/proxy</span><span class="dl">'</span><span class="p">;</span>
<span class="k">export</span> <span class="k">default</span> <span class="nx">Ember</span><span class="p">.</span><span class="nx">Component</span><span class="p">.</span><span class="nf">extend</span><span class="p">({</span>
<span class="na">tagName</span><span class="p">:</span> <span class="dl">'</span><span class="s1">form</span><span class="dl">'</span><span class="p">,</span>
<span class="na">resource</span><span class="p">:</span> <span class="nx">Ember</span><span class="p">.</span><span class="nf">computed</span><span class="p">(</span><span class="dl">'</span><span class="s1">post</span><span class="dl">'</span><span class="p">,</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">BufferedProxy</span><span class="p">.</span><span class="nf">create</span><span class="p">({</span> <span class="na">content</span><span class="p">:</span> <span class="k">this</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="dl">'</span><span class="s1">post</span><span class="dl">'</span><span class="p">)</span> <span class="p">});</span>
<span class="p">}).</span><span class="nf">readOnly</span><span class="p">(),</span>
<span class="na">isNew</span><span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
<span class="na">isEditing</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="nf">focusOut</span><span class="p">()</span> <span class="p">{</span>
<span class="k">if </span><span class="p">(</span><span class="o">!</span><span class="k">this</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="dl">'</span><span class="s1">isNew</span><span class="dl">'</span><span class="p">))</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="dl">'</span><span class="s1">resource</span><span class="dl">'</span><span class="p">).</span><span class="nf">applyChanges</span><span class="p">();</span>
<span class="k">this</span><span class="p">.</span><span class="nf">set</span><span class="p">(</span><span class="dl">'</span><span class="s1">isEditing</span><span class="dl">'</span><span class="p">,</span> <span class="kc">false</span><span class="p">);</span>
<span class="kd">let</span> <span class="nx">action</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="dl">'</span><span class="s1">on-edit</span><span class="dl">'</span><span class="p">);</span>
<span class="k">if </span><span class="p">(</span><span class="k">typeof</span> <span class="nx">action</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">function</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
<span class="nf">action</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="dl">'</span><span class="s1">post</span><span class="dl">'</span><span class="p">),</span> <span class="kd">function</span> <span class="nf">callback</span><span class="p">()</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nf">set</span><span class="p">(</span><span class="dl">'</span><span class="s1">isEditing</span><span class="dl">'</span><span class="p">,</span> <span class="kc">true</span><span class="p">);</span>
<span class="p">}.</span><span class="nf">bind</span><span class="p">(</span><span class="k">this</span><span class="p">));</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="cm">/* … */</span>
<span class="p">});</span>
</code></pre>
<p>The <code>admin.edit</code> route responds to the actions send by the form component. After<br>
the API request is made successfully, the <code>callback</code> function, sent with the action,<br>
is called. Or, an error may be caught by the failed promise.</p>
<p>In the case of an error, the changes to the model are rolled back and the error<br>
response is handled by the route. It depends on the error code whether or not<br>
a transition will be made to an <code>error</code> substate. When an error is not thrown by<br>
a route's model hook, then a transition needs to be made explicitly via <code>catch</code>.</p>
<p>In the example below, it may be a bad user experience to transition away from the<br>
admin form the user is editing - due to a client error (such as "Bad Request" <a href="proxy.php?url=http://httpstatus.es/400" target="_blank" rel="noopener noreferrer">400</a>,<br>
or an "Unprocessable Entity" <a href="proxy.php?url=http://httpstatus.es/422" target="_blank" rel="noopener noreferrer">422</a>). Instead, error properties are set on the<br>
application controller which results in a dismissible error notification.</p>
<p><strong>app/routes/admin/edit.js</strong></p>
<pre class="highlight"><code><span class="k">import</span> <span class="nx">Ember</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">ember</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="nx">ApplicationErrorsMixin</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">jr-test/mixins/application-errors</span><span class="dl">'</span><span class="p">;</span>
<span class="k">export</span> <span class="k">default</span> <span class="nx">Ember</span><span class="p">.</span><span class="nx">Route</span><span class="p">.</span><span class="nf">extend</span><span class="p">(</span><span class="nx">ApplicationErrorsMixin</span><span class="p">,</span> <span class="p">{</span>
<span class="nf">model</span><span class="p">(</span><span class="nx">params</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">store</span><span class="p">.</span><span class="nf">find</span><span class="p">(</span><span class="dl">'</span><span class="s1">posts</span><span class="dl">'</span><span class="p">,</span> <span class="nx">params</span><span class="p">.</span><span class="nx">edit_id</span><span class="p">);</span>
<span class="p">},</span>
<span class="nf">setupController</span><span class="p">(</span><span class="nx">controller</span><span class="p">,</span> <span class="nx">model</span><span class="p">)</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nf">_super</span><span class="p">(</span><span class="nx">controller</span><span class="p">,</span> <span class="nx">model</span><span class="p">);</span>
<span class="nx">controller</span><span class="p">.</span><span class="nf">set</span><span class="p">(</span><span class="dl">'</span><span class="s1">isEditing</span><span class="dl">'</span><span class="p">,</span> <span class="kc">true</span><span class="p">);</span>
<span class="p">},</span>
<span class="na">actions</span><span class="p">:</span> <span class="p">{</span>
<span class="nf">update</span><span class="p">(</span><span class="nx">model</span><span class="p">,</span> <span class="nx">callback</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">store</span><span class="p">.</span><span class="nf">updateResource</span><span class="p">(</span><span class="dl">'</span><span class="s1">posts</span><span class="dl">'</span><span class="p">,</span> <span class="nx">model</span><span class="p">)</span>
<span class="p">.</span><span class="k">finally</span><span class="p">(</span><span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
<span class="k">if </span><span class="p">(</span><span class="k">typeof</span> <span class="nx">callback</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">function</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
<span class="nf">callback</span><span class="p">();</span>
<span class="p">}</span>
<span class="p">})</span>
<span class="p">.</span><span class="k">catch</span><span class="p">(</span><span class="kd">function</span><span class="p">(</span><span class="nx">error</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">model</span><span class="p">.</span><span class="nf">rollback</span><span class="p">();</span>
<span class="k">this</span><span class="p">.</span><span class="nf">send</span><span class="p">(</span><span class="dl">'</span><span class="s1">error</span><span class="dl">'</span><span class="p">,</span> <span class="nx">error</span><span class="p">);</span>
<span class="p">}.</span><span class="nf">bind</span><span class="p">(</span><span class="k">this</span><span class="p">));</span>
<span class="p">},</span>
<span class="nf">error</span><span class="p">(</span><span class="nx">error</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if </span><span class="p">(</span><span class="nx">error</span><span class="p">.</span><span class="nx">code</span> <span class="o">===</span> <span class="mi">422</span> <span class="o">||</span> <span class="nx">error</span><span class="p">.</span><span class="nx">code</span> <span class="o">===</span> <span class="mi">400</span><span class="p">)</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nf">handleApplicationError</span><span class="p">(</span><span class="nx">error</span><span class="p">);</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nf">intermediateTransitionTo</span><span class="p">(</span><span class="dl">'</span><span class="s1">admin.edit_error</span><span class="dl">'</span><span class="p">,</span> <span class="nx">error</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">});</span>
</code></pre>
<p>So that both the <em>admin/edit</em> and <em>admin/create</em> routes can use the same behavior,<br>
the method <code>handleApplicationError</code> is defined in a mixin.</p>
<p>This mixin is used to parse the error responses and format error details.</p>
<p><strong>app/mixins/application-errors.js</strong></p>
<pre class="highlight"><code><span class="k">import</span> <span class="nx">Ember</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">ember</span><span class="dl">'</span><span class="p">;</span>
<span class="k">export</span> <span class="k">default</span> <span class="nx">Ember</span><span class="p">.</span><span class="nx">Mixin</span><span class="p">.</span><span class="nf">create</span><span class="p">({</span>
<span class="nf">handleApplicationError</span><span class="p">(</span><span class="nx">error</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">let</span> <span class="nx">details</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nf">handleUnprocessableEntities</span><span class="p">(</span><span class="nx">error</span><span class="p">);</span>
<span class="nx">details</span> <span class="o">=</span> <span class="nx">details</span> <span class="o">||</span> <span class="k">this</span><span class="p">.</span><span class="nf">handleBadRequest</span><span class="p">(</span><span class="nx">error</span><span class="p">);</span>
<span class="k">this</span><span class="p">.</span><span class="nf">controllerFor</span><span class="p">(</span><span class="dl">'</span><span class="s1">application</span><span class="dl">'</span><span class="p">).</span><span class="nf">setProperties</span><span class="p">({</span>
<span class="dl">'</span><span class="s1">errorMessage</span><span class="dl">'</span><span class="p">:</span> <span class="nx">error</span><span class="p">.</span><span class="nx">message</span><span class="p">,</span>
<span class="dl">'</span><span class="s1">errorDetails</span><span class="dl">'</span><span class="p">:</span> <span class="nx">details</span> <span class="o">||</span> <span class="kc">undefined</span>
<span class="p">});</span>
<span class="p">},</span>
<span class="nf">handleBadRequest</span><span class="p">(</span><span class="nx">error</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if </span><span class="p">(</span><span class="nx">error</span><span class="p">.</span><span class="nx">code</span> <span class="o">!==</span> <span class="mi">400</span> <span class="o">||</span> <span class="o">!</span><span class="nx">error</span><span class="p">.</span><span class="nx">errors</span><span class="p">.</span><span class="nx">length</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span><span class="p">;</span> <span class="p">}</span>
<span class="c1">// See https://github.com/cerebris/jsonapi-resources#error-codes</span>
<span class="kd">let</span> <span class="nx">errors</span> <span class="o">=</span> <span class="nx">error</span><span class="p">.</span><span class="nx">errors</span><span class="p">.</span><span class="nf">filterBy</span><span class="p">(</span><span class="dl">'</span><span class="s1">code</span><span class="dl">'</span><span class="p">,</span> <span class="mi">105</span><span class="p">);</span>
<span class="nx">errors</span> <span class="o">=</span> <span class="nx">errors</span><span class="p">.</span><span class="nf">mapBy</span><span class="p">(</span><span class="dl">'</span><span class="s1">detail</span><span class="dl">'</span><span class="p">);</span>
<span class="k">return </span><span class="p">(</span><span class="o">!</span><span class="nx">errors</span><span class="p">)</span> <span class="p">?</span> <span class="dl">''</span> <span class="p">:</span> <span class="nx">errors</span><span class="p">.</span><span class="nf">join</span><span class="p">(</span><span class="dl">'</span><span class="s1"> </span><span class="dl">'</span><span class="p">);</span>
<span class="p">},</span>
<span class="nf">handleUnprocessableEntities</span><span class="p">(</span><span class="nx">error</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if </span><span class="p">(</span><span class="nx">error</span><span class="p">.</span><span class="nx">code</span> <span class="o">!==</span> <span class="mi">422</span> <span class="o">||</span> <span class="o">!</span><span class="nx">error</span><span class="p">.</span><span class="nx">errors</span><span class="p">.</span><span class="nx">length</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span><span class="p">;</span> <span class="p">}</span>
<span class="c1">// See https://github.com/cerebris/jsonapi-resources#error-codes</span>
<span class="kd">let</span> <span class="nx">errors</span> <span class="o">=</span> <span class="nx">error</span><span class="p">.</span><span class="nx">errors</span><span class="p">.</span><span class="nf">filterBy</span><span class="p">(</span><span class="dl">'</span><span class="s1">code</span><span class="dl">'</span><span class="p">,</span> <span class="mi">100</span><span class="p">);</span>
<span class="kd">let</span> <span class="nx">fields</span> <span class="o">=</span> <span class="nx">errors</span><span class="p">.</span><span class="nf">map</span><span class="p">(</span><span class="kd">function</span><span class="p">(</span><span class="nx">error</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">let</span> <span class="nx">paths</span> <span class="o">=</span> <span class="nx">error</span><span class="p">.</span><span class="nx">source</span><span class="p">.</span><span class="nx">pointer</span><span class="p">.</span><span class="nf">split</span><span class="p">(</span><span class="dl">'</span><span class="s1">/</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">let</span> <span class="nx">attr</span> <span class="o">=</span> <span class="nx">paths</span><span class="p">[</span><span class="nx">paths</span><span class="p">.</span><span class="nx">length</span> <span class="o">-</span> <span class="mi">1</span><span class="p">].</span><span class="nf">split</span><span class="p">(</span><span class="dl">'</span><span class="s1">_</span><span class="dl">'</span><span class="p">);</span>
<span class="nx">attr</span> <span class="o">=</span> <span class="nx">attr</span><span class="p">.</span><span class="nf">map</span><span class="p">(</span><span class="kd">function</span><span class="p">(</span><span class="nx">str</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">Ember</span><span class="p">.</span><span class="nb">String</span><span class="p">.</span><span class="nf">capitalize</span><span class="p">(</span><span class="nx">str</span><span class="p">);</span>
<span class="p">});</span>
<span class="k">return</span> <span class="nx">attr</span><span class="p">.</span><span class="nf">join</span><span class="p">(</span><span class="dl">'</span><span class="s1"> </span><span class="dl">'</span><span class="p">);</span>
<span class="p">});</span>
<span class="k">return </span><span class="p">(</span><span class="o">!</span><span class="nx">fields</span><span class="p">)</span> <span class="p">?</span> <span class="dl">''</span> <span class="p">:</span> <span class="dl">'</span><span class="s1">Invalid fields: </span><span class="dl">'</span> <span class="o">+</span> <span class="nx">fields</span><span class="p">.</span><span class="nf">join</span><span class="p">(</span><span class="dl">'</span><span class="s1">, </span><span class="dl">'</span><span class="p">)</span> <span class="o">+</span> <span class="dl">'</span><span class="s1">.</span><span class="dl">'</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">});</span>
</code></pre>
<p>For the <em>admin/create</em> route no <code>error</code> substate template was needed. For handling<br>
<a href="proxy.php?url=http://httpstatus.es/500" target="_blank" rel="noopener noreferrer">500</a> errors the parent application <code>error</code> substate will be used. And, for<br>
handling <a href="proxy.php?url=http://httpstatus.es/400" target="_blank" rel="noopener noreferrer">400</a> or <a href="proxy.php?url=http://httpstatus.es/422" target="_blank" rel="noopener noreferrer">422</a> responses it makes sense to display the errors "in-context",<br>
without making a transition to a substate. The only reason the <code>admin.edit_error</code><br>
substate uses the <code>admin/edit/error.hbs</code> template is to handle a <a href="proxy.php?url=http://httpstatus.es/404" target="_blank" rel="noopener noreferrer">404</a> and a <a href="proxy.php?url=http://httpstatus.es/500" target="_blank" rel="noopener noreferrer">500</a><br>
response. That is not the case with the <code>admin.create</code> route; the parent substate,<br>
<code>application_error</code> will work just fine.</p>
<p>Notice the naming convention used with ember-cli, the substates use a <code>.</code> period<br>
and <code>_</code> for the substate name and the templates use <code>/</code> and <code>-</code>. So, to transition<br>
to the application error substate use <code>application_error</code> like so:<br>
<code>this.intermediateTransitionTo('application_error', err)</code>, or to a nested substate:<br>
<code>this.intermediateTransitionTo('admin.edit_error', err)</code>.</p>
<h2>Take-Aways…</h2>
<p>The <code>ApplicationRoute</code> <code>error</code> action handler can be used to catch and handle<br>
various errors and delegate the notification of the errors to the<br>
<code>ApplicationController</code> and accompanying HTMLBars application template. Or, an<br>
<code>applicaton_error</code> substate with an <code>applicaton-error</code> template may handle<br>
route <code>error</code> events.</p>
<p>Also, you may combine both <code>error</code> substates and <code>error</code> action handling strategies<br>
in a creative way my sending the <code>error</code> event when the error occurs as the result<br>
of another action; instead of by a model hook method, e.g. <code>model</code>, <code>beforeModel</code>,<br>
<code>afterModel</code>, etc.</p>
<p>I favor the using <code>error</code> substates as the primary strategy for fault tolerance<br>
in an ember application. I also like the fact that the <code>error</code> action may be<br>
utilized creatively as a secondary strategy.</p>
<p>In the <a href="proxy.php?url=https://github.com/pixelhandler/ember-jsonapi-resources" target="_blank" rel="noopener noreferrer">Ember JSONAPI Resources</a> addon a <a href="proxy.php?url=https://github.com/pixelhandler/ember-jsonapi-resources/blob/master/addon/utils/errors.js" target="_blank" rel="noopener noreferrer">ErrorMixin</a> defines error types for:</p>
<ul>
<li><code>ServerError</code> - handles 50x</li>
<li><code>ClientError</code> - handles 40x</li>
<li><code>FetchError</code> (default failure) - handles 30x</li>
</ul>
<p>The error objects thrown by a resource's <code>service</code> includes the error code.<br>
You can use the <code>code</code> to determine how to present the error notification, use<br>
the error type, or use the <code>error.name</code> property. The <code>error.code</code> the most specific.</p>
<p>Based on the <code>error.code</code> the route <code>error</code> action can transition to a custom route<br>
to handle that specific error type. In the first example, a transition is made to<br>
a <code>/not-found</code> route, that could have used an <code>intermediateTransitionTo</code> to keep<br>
the URL unchanged.</p>
<p>However, there is a catch when using a route's <code>error</code> action, by doing so the route<br>
<code>error</code> substates are not used. The second solution does not use use the<br>
route <code>error</code> action. Instead, it uses route <code>error</code> substates with <code>error</code> templates.</p>
<p>Using the <code>error</code> substates allows the use of the <code>route-name_error</code> state and<br>
associated <code>route-name-error</code> template. The <code>setupController</code> hook of the <code>*_error</code><br>
route receives objects: <code>controller</code> and <code>error</code> (as it's <code>model</code>). Using the route<br>
<code>error</code> substate to define properties for the error template is a good way to<br>
customize the display of the error notifications, depending on the error code.</p>
<p>When the error is not thrown by one of the route model hooks, perhaps by a custom<br>
<code>action</code>, you can decide how to handle the error. If the error is recoverable<br>
perhaps set properties on the application controller for display by the application<br>
template. Or, if the error is not recoverable perhaps fire the <code>error</code> event on<br>
the route, e.g. <code>this.send('error', resp);</code></p>
<p>(One caveat - the current state of using an acceptance test for an error substate<br>
is that the test may fail. Any error may cause your test adapter to fail the test.<br>
See <a href="proxy.php?url=https://github.com/emberjs/ember.js/issues/12791" target="_blank" rel="noopener noreferrer">12791</a>.)</p>
<p>The <a href="proxy.php?url=http://ember-jsonapi-resources.com" target="_blank" rel="noopener noreferrer">ember-jsonapi-resources</a> addon uses custom error objects to make fault tolerance<br>
first class in your ember application.</p>
<h3>Further Reading…</h3>
<ul>
<li><a href="proxy.php?url=https://guides.emberjs.com/v2.2.0/routing/loading-and-error-substates/#toc_code-error-code-substates" target="_blank" rel="noopener noreferrer">Ember.js Loading and Error Substates</a></li>
<li><a href="proxy.php?url=http://jsonapi.org/examples/#error-objects-error-codes" target="_blank" rel="noopener noreferrer">JSON API Error Objects</a></li>
<li><a href="proxy.php?url=https://github.com/cerebris/jsonapi-resources#error-codes" target="_blank" rel="noopener noreferrer">JSONAPI::Resources Error Codes</a></li>
<li><a href="proxy.php?url=http://guides.rubyonrails.org/layouts_and_rendering.html" target="_blank" rel="noopener noreferrer">Rails Layouts and Rendering</a></li>
<li><a href="proxy.php?url=https://github.com/emberjs/ember.js/issues/12791#issuecomment-170675020" target="_blank" rel="noopener noreferrer">Ember.js Acceptance Testing Issue</a></li>
<li><a href="proxy.php?url=https://guides.emberjs.com/v2.2.0/configuring-ember/debugging/" target="_blank" rel="noopener noreferrer">Ember.js Debugging</a></li>
</ul>
My Battle with Data Persistence in Ember Appshttps://pixelhandler.dev/posts/my-battle-with-data-persistence-in-ember-apps2015-06-20T00:00:00+00:002015-06-20T00:00:00+00:00Billy HeatonI worked at an agency on a complex single page application in JavaScript that was a product finder. We built the API so that is was discoverable, parameters came in and choices came out in JSON wit...<h1>Project Wars</h1>
<p>I worked at an agency on a complex single page application in JavaScript that was a product finder. We built the API so that is was discoverable, parameters came in and choices came out in JSON with URLs for the next available choices or actions. After a few steps, maybe seven or so, the user would have a narrow set of products that met the search criteria and geographic availability. The application loaded JSON files to configure the steps it took to walk the user through the various product finders. The functional spec was in the range of 350 pages, and the project was a MICROSITE. The end result was an order page. There shoppers could begin the purchase process and customize the products' size, to fit the shoppers custom measurements. How did we do this? We used Backbone.js and built all of our application logic on top of it. Just prior to this project I had developed a front-end framework on top of Backbone that did have many similarities to Ember.js. It supported building modular packages with AMD using Require.js. Those were the days of the Wild West, when we rolled our own JavaScript front-end frameworks. Hallelujah, those days are gone. I say, "Good riddance."</p>
<p>JavaScript frameworks provide a way to build; and some will dive into the data layer, while others leave that to the developer. I have to say that, the data side of building single-page (JavaScript) applications is still a lot like that last paragraph, in the Wild West. Building on top of something that mostly works but doesn't answer the direct needs of my application. Basically, building with an alpha/beta library and chasing a solution.</p>
<h2>The Awakening</h2>
<p>So after about two years, full-time, of focusing on building applications with Ember.js and doing battle with complex data needs (lots of read/write, multiple users in an app, big data, legacy data, magic data, think of a gem, and simple data)… I am now extremely thirsty for a common specification that works well for applications speaking JSON over HTTP. I'd like a specification, or format, that works really well in a JavaScript application and allows for data services to have the flexibility and freedom to own the data needs entirely. Those data services simply give a representation of resources as the client requests them - as well as provide operations to those resources' attributes, state and relationships via REST. Specifically, I would like the client to have no requirement to duplicate the ORM (Object-relational mapping) or to mimic how the backend maps relations (foreign keys). Ideally, services that provide a solid format for JSON data, are discoverable (provide URLs), and have a pattern for operating on the resources, etc. And of course, without the need to battle over data contracts between client and server (teams).</p>
<h2>The Menace</h2>
<p>So back to my history. After building the antithesis of a microsite, I worked on an email application built with Ember. We built an Ember app inside an Ember app for editing an HTML document (email template). We broke down an HTML document and turned it into editable views; then persisted the changed document so that it is ready to merge with data for mail delivery. Ember Data was in its first iteration and had a concept of a transactions, and still needed to define how to work with polymorphic data. The team was an early adopter of Ember.js, pre 1.x. The team fought with bugs in basically alpha software for data persistence but still managed to ship a beta product. Well, it turned out that that the team was not the right fit for me; and I moved on. However, I was able to adopt Ember.js early, before it hit a stable 1.0 release, this made me happy.</p>
<h2>Attack at the Agency</h2>
<p>Next, I took a position about 300 miles closer to home but still about 60 miles away. I do like working with Ember.js so tried to continue that effort. This was a small team, who previously built apps with SproutCore, the predecessor to Ember.js. The data relationships were complex, the need was for lots of reading and writing, as well as dealing with sparse record collections. There was a pattern of caching related data to be used in a list view by requesting an incomplete representations of a resource (for large lists) and reloading some records to refresh for detail views. We managed the state of resources in both the client and the server. We embedded snippets of data across various documents, managed the relationships of the data on the client, and we wished for id-only records (to deal with the sparse collections). We persisted collections of related records in batches, and disabled editing during the transaction.</p>
<p>There was the need to overhaul Ember Data and then it happened. The library abandoned transactions and embedded records. We tried to press forward anyway, our data needs did not change, the library changed its feature set. Perhaps the ambition needed to be simplified; we had dreams of a super ambitions data solution. So, we hacked up our feature set on top of the library we chose, Ember Data. We thought that this was the happy path for us.</p>
<p>Also we tried out a move to Ember Model, perhaps a lightweight solution would be sufficient. JSONPatch became appealing to us, as we fought with data inconsistencies from throwing around complete representations of objects asynchronously for both attribute changes and relationship changes. We spent about six months on a fork of an adapter/serializer combination that fixed the embedded records solution for Ember Data. It was a happy day when that work merged back into Ember Data. A sweet win for us and others who had similar needs for embedding records. Then the contract ended, another transition for me - I really wanted to work closer to home. (I have a big family.)</p>
<h2>Big Data Strikes Back</h2>
<p>Next, I moved to another early adopter of Ember.js, to work on ambitions applications utilizing big data. The apps consumed a big data set. Some data was input via file uploads, from UI input, and other data was changed/added by another source in the cloud platform - all of the changes require the app to be refreshed. This was another complex data solution using an early version of Ember Data which required a server solution to re-format the data accordingly to the client (data) conventions. Next, we built newer and smaller apps, yet as a collection of tools still ambitions in scope. We abandoned Ember Data for this work and built custom adapters to connect with our data services. With a big set of data… endpoints vary across the platform. Some of our requests require two or more requests to compose the resources in order to hand them off to the client application. We built "thick" client apps that work with many endpoints to capture slices of the large data set.</p>
<p>From these examples, I can see why there is not a single solution for everyone's use cases for data persistence. And why anyone working on a common data persistence solution is required to build a complex abstraction to address so many options to communication JSON resources via HTTP.</p>
<h2>The Return to the Agency</h2>
<p>Strange as it may seem, I actually went back to my old job. A friend opened up his seat making a way toward the flexibility I needed with my family and a stretch goal in my career in accepting a management role. I thought I'd never do that. On the microsite project I had a lead role. During that season I hardly wrote any code and ended up frustrated, the deadlines were nutty and the tech debt was steep. I really do like to build stuff, especially web applications. Now, I'm back in LA. I work remote most of the time and lead a small team. We have a handful of Ember apps that have lived for one or two years. These apps use a core library, so there are many shared components/views/controllers for UI needs, including managing large collections of resources. I am really happy :)</p>
<p>We still have a lot of work ahead of us. The apps we work on were sort of built in start-up mode. We need automated tests, this will help us stabilize change - and some refactoring. We'd like to move to <a href="proxy.php?url=http://ember-cli.com" target="_blank" rel="noopener noreferrer">Ember CLI</a> quickly. We now have Ember 2.0-beta to embrace; so basically there are huge shifts that we need to make on our platform. To move to the new "Ember Way" because Ember 1.x is so last year. By the way, if you thought there was such a thing as an Ember Way, I'm sorry to disappoint you there is no such thing. The Web platform is moving fast these days and Ember.js does strive to skate to where the puck is going. So yeah change happens, well it happens a lot, that's the Ember Way. We usually ride canary or beta versions of Ember then when an app ships track to the current release. Over the past few years our apps made it all the way to 1.10. I've seen others that have not made it that far.</p>
<h2>More Movie Puns</h2>
<p>I have seen a few big Ember apps frozen (I know what you're thinking, "Let it go"). The freeze was due to loads of features that integrate with lots of data (while making big bets on Ember Data). When Ember Data was revamped the first time, there became too much effort to revamp the entire app and freeze shipping user stories. Perhaps it was just unfortunate timing for the product backlog. Just because the library changed, doesn't mean the business was ready to freeze the backlog while overhauling the app's data persistence solution. (To the product owners it sounds a lot like asking "Do you want to build a snowman?").</p>
<p>Maybe my experience is unique; however, I've heard similar stories where a major refactor had to take place, and was required to continue feature development. That makes sense, when the framework/library changes significantly, upgrades should be planned accordingly. Perhaps that is an early adoption tax, par for the course. Also, every project has an end of life; that's the perspective of the long game. One of my business coaches told me, "You always need an exit strategy". So I try to plan for how an app will end and make trade offs accordingly. At times it's ok not to track the current framework release and at other times that would kill the product.</p>
<h2>The Good Bad and the Ugly</h2>
<p>Experience teaches me to learn from the good parts… What would my ideal data persistence solution look like? Is there actually one? Or, does the journey always depend on the project requirements? That may be true. Most projects are not done perfectly the first time, maybe the second, or perhaps the third. The Ember Route had three big overhauls and so did the view layer.</p>
<p>Over the past month a few quotes have captured my attention…</p>
<p>“Don't try to be the smartest developer. Instead, be the most tenacious one. Be the one who doesn't give up until the problem is solved.” –Dana Jones @danabrit <a href="proxy.php?url=https://twitter.com/danabrit/status/606563836007571457" target="_blank" rel="noopener noreferrer">Jun 4, 2015</a></p>
<p>“Anything worth doing is worth doing poorly until you learn to do it well.” –Zig Ziglar</p>
<p>“Success is when people who don't understand your tool are using it – so you have to plan for that” –@stubbornella <a href="proxy.php?url=https://twitter.com/chriseppstein/status/606297381529747456" target="_blank" rel="noopener noreferrer">#theMixinJune2015</a></p>
<p>As I have considered the idea of starting from scratch I collected a list of what I think would be needed for my current projects and future work.</p>
<h2>The Bucket List</h2>
<p>Here's a few things that I imagine would be included in my ideal persistence solution…</p>
<ul>
<li>My API server defines the resources for my client application(s) to consume</li>
<li>My client application(s) understand & treat resources as first class, having no need to transform the resource to yet another representation</li>
<li>Wait, no transformation? Yeah, unless I want to, I do not want to be forced to.</li>
<li>Only define my ORM concerns in one place on the server, not two (again on the client)</li>
<li>I expect that less code means less bugs (I've seen a lot of bugs in data persistence layers)</li>
<li>A thin codebase with nomenclature that is shared with the API format for a resource and it's operations</li>
<li>During introspection of the representation of a resource within the client app, the object should have the same structure and nomenclature that the server defines for the resource</li>
<li>My client app(s) should define the caching concerns for the requests resource(s) and have the option to honor the cache headers; 304's are fine. HTTP has solved caching already</li>
<li>Optionally minimize redundant requests when making a query for a group of related resources</li>
<li>Perhaps the caching strategy can be a mixin that works together with the resources which have properties to indicate when they are expired</li>
<li>I want a service object that is injected wherever I interact with the resources, and a singleton for each service that has an event bus to communicate with all the resource instances of the same type. For example to make updates as close to real time as possible by PATCHing the resource</li>
<li>I want to define the resource's states, some common states will work like <code>isDirty</code></li>
<li>I want the resource to know when it has changed and send that event to the service</li>
<li>I need a simple way to compute properties from the resource attributes to meet the needs of the client application</li>
<li>I want relationships between resources to be defined in a simple way and for the resources to know how to operate on its relations, like adding/removing relations as independent actions, totally separate from persisting the attributes of the resource itself</li>
<li>I'd like a store service that is a facade to all the services that are used by the various resources, one point of service to call methods for fetching, crud operations, as well as operations on the resource's relation(s)</li>
<li>I'd like each service to house it's own cache for it's type (entity) as well as track any meta data associated with the collection or per request</li>
<li>The resources should keep track of their own meta data, relationships and links. The service(s) should not need to construct URLs for all the operations on a resource</li>
<li>Oh, and I want URLs to be first class, The API server sends the links that the client should use</li>
<li>Perhaps I can abandon XHR and go with <a href="proxy.php?url=http://davidwalsh.name/fetch" target="_blank" rel="noopener noreferrer">window.fetch</a></li>
</ul>
<h2>Back to the Present</h2>
<p>Now let's fast forward to today… Meet <a href="proxy.php?url=http://jsonapi.org" target="_blank" rel="noopener noreferrer">JSON API</a>, version 1.0 - A new specification. “Your anti-bikeshedding weapon.” Wow, two years of hammering out a specification for representing objects in JSON over HTTP (REST). The spec looks solid, I've taken it for a test drive over the last year - kicking the tires and testing out the spec as it has taken shape. I'm very impressed and happy with this specification. I imagine that it should last for some time, perhaps years to come. Maybe I'll bring my current projects up to the spec and later build out new application servers using a different language but use the same JSON API specification. Sounds promising, doesn't it!</p>
<p>But wait, aren’t you supposed to use Ember Data? My suggestion is that you don’t let someone decide for you. Seek a solution that fits the problem domain of your project needs. That might be Ember Data, for me it is not; at lease not at 1.x or 2.x. Maybe the 3rd time will be the charm. What I'm interested in is a simple solution that is written from the ground up to fit the <a href="proxy.php?url=http://jsonapi.org/format/" target="_blank" rel="noopener noreferrer">format of the JSON API 1.0 Specification</a>, period. So, I can just get straight to work… shipping user stories (doing less bug hunting in the data library).</p>
<p>I've convinced myself to build a persistence solution, even though there is one that has existed for a couple years and has had about 300 contributors, including myself. And even though that solution has hit a (stable) release tag of v1.13, while also tagged for 2.0-beta on the same day. Seriously I'm not bitter or anything, <a href="proxy.php?url=https://github.com/emberjs/data" target="_blank" rel="noopener noreferrer">Ember Data</a> is an excellent library. But what I've come to realize is that Ember Data does not provide the solution I am looking for. Ember Data provides a great adapter/serializer pattern to connect any type of data service to your Ember app. Ember Data thinks like an active record ORM; at least in nomenclature; <code>belongsTo</code> and <code>hasMany</code> remind me that Ember Data needs foreign keys as a primary way to connect relationships vs. URLs. I'm a web guy, I like URLs. URLs are the main selling point and reason I adopted Ember.js in the first place. The <a href="proxy.php?url=http://guides.emberjs.com/v1.12.0/routing/" target="_blank" rel="noopener noreferrer">Ember.Router</a> has great URL support from the start.</p>
<h2>A Battle Plan Emerges</h2>
<p>So here’s my plan… Do only what the server's specification defines for it's resources, and do only that. To simplify the mental model for the client(s) that collaborate with the resources via server app(s). To fire the middleman, reduce the abstractions, reduce barriers for developers to ramp up on a project, minimize the code base which is responsible for the data layer. To build a direct path from point A to point B (client/server). I will use a solution that is direct and concise, with a strong set of features, proven solutions, and reserve the freedom add in my needs for caching and acting on state/events that make sense to the application's needs.</p>
<p>Actually this is not that hard, <a href="proxy.php?url=http://emberjs.com" target="_blank" rel="noopener noreferrer">Ember.js</a> has great primitives for some very advanced behaviors, like the <a href="proxy.php?url=http://emberjs.com/api/classes/Ember.PromiseProxyMixin.html" target="_blank" rel="noopener noreferrer">Ember.PromiseProxyMixin</a>, <a href="proxy.php?url=http://emberjs.com/api/classes/Ember.Evented.html" target="_blank" rel="noopener noreferrer">Ember.Evented</a>, <a href="proxy.php?url=http://emberjs.com/api/classes/Ember.Service.html" target="_blank" rel="noopener noreferrer">Ember.Service</a> and <a href="proxy.php?url=http://guides.emberjs.com/v1.12.0/object-model/classes-and-instances/" target="_blank" rel="noopener noreferrer">Ember.Object</a> prototypes. It would be hard if I did not have a few years of experience with the framework. With that experience I find a lot of power in using the Ember.js framework. <a href="proxy.php?url=http://guides.emberjs.com/v1.12.0/understanding-ember/dependency-injection-and-service-lookup/" target="_blank" rel="noopener noreferrer">Dependency injection</a> is easy, the concept of services are first class, the testing story is excellent now, the <a href="proxy.php?url=http://emberjs.com/api/classes/Ember.Application.html#property_container" target="_blank" rel="noopener noreferrer">application.container</a> is my friend I can create factories for my resources and <a href="proxy.php?url=http://emberjs.com/api/classes/Ember.Application.html#method_register" target="_blank" rel="noopener noreferrer">register</a> singletons for my services.</p>
<p>So now I am taking the posture of a Samurai Warrior – “dying before going into battle.” The warrior enters combat event without fear of death, acceptance of his own death in advance. I tell myself, “Don't become like water my friend, instead become a dead man walking”. I will unconditionally commit to success in battle without worrying about survival. What I am about to describe is my tactical response to adopting the new JSON API specification, from the ground up. Yes, going forward, I will roll my own data persistence library – because I can and it's no longer hard to do. If the project dies so be it.</p>
<h2>A Man Can Change His Destiny</h2>
<p>“The perfect blossom is a rare thing. You could spend your life looking for one, and it would not be a wasted life.” –Katsumoto (The Last Samurai)</p>
<p>Well I don't believe there is a perfect data persistence solution and won't suggest that I can create a perfect solution either. But, I will pursue it.</p>
<h2>The Nitty Gritty</h2>
<p>Let's just assume that I could create an elegant solution. How would I do that using Ember.js?</p>
<p>First I'd get intimate with the <a href="proxy.php?url=http://jsonapi.org/format/" target="_blank" rel="noopener noreferrer">JSON API format</a> Then I'd create a <a href="proxy.php?url=http://jsonapi.org/format/#document-resource-objects" target="_blank" rel="noopener noreferrer">Resource Object</a> as my base model prototype in an Ember app. I will begin by building an <a href="proxy.php?url=http://www.ember-cli.com/#developing-addons-and-blueprints" target="_blank" rel="noopener noreferrer">Ember CLI addon</a> for the <code>Resource</code> and whatever else I need.</p>
<h3>A Resource Object</h3>
<p>A Resource (or 'model') can resemble the structure of an object as defined by the JSON API 1.0 Specification. The <code>Resource</code> prototype creates hashes for: <code>attributes</code>, <code>relationships</code>, <code>links</code>, <code>meta</code>, and has attributes: <code>id</code> and <code>type</code>. The properties and structure of a model created by extending <code>Resource</code> closely follow the objects defined by the JSON API specification.</p>
<p>It would be used like so:</p>
<pre class="highlight"><code><span class="k">export</span> <span class="k">default</span> <span class="nx">Resource</span><span class="p">.</span><span class="nf">extend</span><span class="p">({</span>
<span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">post</span><span class="dl">'</span><span class="p">,</span>
<span class="na">service</span><span class="p">:</span> <span class="nx">Ember</span><span class="p">.</span><span class="nx">inject</span><span class="p">.</span><span class="nf">service</span><span class="p">(</span><span class="dl">'</span><span class="s1">posts</span><span class="dl">'</span><span class="p">),</span>
<span class="na">title</span><span class="p">:</span> <span class="nf">attr</span><span class="p">(),</span>
<span class="na">date</span><span class="p">:</span> <span class="nf">attr</span><span class="p">(),</span>
<span class="na">excerpt</span><span class="p">:</span> <span class="nf">attr</span><span class="p">(),</span>
<span class="na">body</span><span class="p">:</span> <span class="nf">attr</span><span class="p">(),</span>
<span class="na">slug</span><span class="p">:</span> <span class="nf">attr</span><span class="p">(),</span>
<span class="na">author</span><span class="p">:</span> <span class="nf">hasOne</span><span class="p">(</span><span class="dl">'</span><span class="s1">author</span><span class="dl">'</span><span class="p">),</span>
<span class="na">comments</span><span class="p">:</span> <span class="nf">hasMany</span><span class="p">(</span><span class="dl">'</span><span class="s1">comments</span><span class="dl">'</span><span class="p">)</span>
<span class="p">});</span>
</code></pre>
<p>I want helpers to <code>get</code> and <code>set</code> the <code>attributes</code>, I'll use an <code>attr()</code> helper method to set that up. That method can track changes as properties are set and manage the current/previous attributes so I can know whether the instance has changed attributes. And by injecting a service I can emit events like <code>attributeChanged</code> so the service can persist the change in as close to real time as possible.</p>
<p>The Ember CLI addon can define <code>generators</code> so I can scaffold the prototypes for my resources, like the above example. I can just use <code>ember generate resource entityName</code></p>
<p>The resource can have <code>addRelationship</code> and <code>removeRelationship</code> methods to compose the relationships that the resource is associated with. The <code>hasOne</code> and <code>hasMany</code> helpers can setup the computed properties that utilize the <a href="proxy.php?url=http://emberjs.com/api/classes/Ember.PromiseProxyMixin.html" target="_blank" rel="noopener noreferrer">Ember.PromiseProxyMixin</a> to find the related resource(s) based on the URLs in the resource's <a href="proxy.php?url=http://jsonapi.org/format/#document-resource-object-relationships" target="_blank" rel="noopener noreferrer">relationships</a> object which contains <a href="proxy.php?url=http://jsonapi.org/format/#document-links" target="_blank" rel="noopener noreferrer">links</a> object(s)</p>
<p>The resource can have an <code>isCacheExpired</code> property so that if the service keeps a reference or <code>cache</code> of it, the 'staleness' of the data can be found out.</p>
<p>And using the <code>attributes</code> hash of the <code>resource</code> instance I can compute properties from it for using in my templates that are read-only like so:</p>
<pre class="highlight"><code><span class="k">export</span> <span class="k">default</span> <span class="nx">Resource</span><span class="p">.</span><span class="nf">extend</span><span class="p">({</span>
<span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">comment</span><span class="dl">'</span><span class="p">,</span>
<span class="na">service</span><span class="p">:</span> <span class="nx">Ember</span><span class="p">.</span><span class="nx">inject</span><span class="p">.</span><span class="nf">service</span><span class="p">(</span><span class="dl">'</span><span class="s1">comments</span><span class="dl">'</span><span class="p">),</span>
<span class="na">body</span><span class="p">:</span> <span class="nf">attr</span><span class="p">(),</span>
<span class="na">date</span><span class="p">:</span> <span class="nx">Ember</span><span class="p">.</span><span class="nf">computed</span><span class="p">(</span><span class="dl">'</span><span class="s1">attributes</span><span class="dl">'</span><span class="p">,</span> <span class="p">{</span>
<span class="nf">get</span><span class="p">()</span> <span class="p">{</span>
<span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="dl">'</span><span class="s1">attributes.created-at</span><span class="dl">'</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}).</span><span class="nf">readOnly</span><span class="p">(),</span>
<span class="na">commenter</span><span class="p">:</span> <span class="nf">hasOne</span><span class="p">(</span><span class="dl">'</span><span class="s1">commenter</span><span class="dl">'</span><span class="p">),</span>
<span class="na">post</span><span class="p">:</span> <span class="nf">hasOne</span><span class="p">(</span><span class="dl">'</span><span class="s1">post</span><span class="dl">'</span><span class="p">)</span>
<span class="p">});</span>
</code></pre>
<p>A new resource may be generated in a route model hook like so:</p>
<pre class="highlight"><code><span class="nf">model</span><span class="p">()</span> <span class="p">{</span>
<span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">container</span><span class="p">.</span><span class="nf">lookup</span><span class="p">(</span><span class="dl">'</span><span class="s1">model:posts</span><span class="dl">'</span><span class="p">).</span><span class="nf">create</span><span class="p">({</span>
<span class="na">isNew</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="na">attributes</span><span class="p">:</span> <span class="p">{</span> <span class="na">date</span><span class="p">:</span> <span class="k">new</span> <span class="nc">Date</span><span class="p">()</span> <span class="p">}</span>
<span class="p">});</span>
<span class="p">}</span>
</code></pre>
<p>Notice that the <code>attributes</code> hash is used to set initial properties. The computed properties created with the <code>attr()</code> helper are used as accessors to get/set rather than to create. That's how the resource was represented in JSON from the server anyway, resource attributes belong to the <code>attributes</code> object, I'll just keep it like that for the sake of simplicity.</p>
<p>Now that I have a base model or <code>Resource</code> with helpers to compose the properties that are meant to be used in the user interface (given to the template) I can setup an <code>Adapter</code> to fetch resources from a API server.</p>
<h3>An Adapter Object</h3>
<p>This should be a singleton instance for each type of entity (or resource) that can be fetched and/or persisted.</p>
<p>Given that an Ember.js app may be hosted as a stand-alone client application and may choose to use use a proxy to bring an API server into it's own domain. It's likely that I may need a couple hooks manipulate the URL's in the <code>relationships</code> object of a resource - since it's required in a browser to work within the same domain. Unless you really want to use CORs (cross-origin resource sharing).</p>
<p>Imagine I have a <code>ApplicationAdapter</code> that is already setup to work with my JSON API server, I could use something like this:</p>
<pre class="highlight"><code><span class="k">import</span> <span class="nx">ApplicationAdapter</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./application</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="nx">config</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../config/environment</span><span class="dl">'</span><span class="p">;</span>
<span class="k">export</span> <span class="k">default</span> <span class="nx">ApplicationAdapter</span><span class="p">.</span><span class="nf">extend</span><span class="p">({</span>
<span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">post</span><span class="dl">'</span><span class="p">,</span>
<span class="na">url</span><span class="p">:</span> <span class="nx">config</span><span class="p">.</span><span class="nx">APP</span><span class="p">.</span><span class="nx">API_PATH</span> <span class="o">+</span> <span class="dl">'</span><span class="s1">/posts</span><span class="dl">'</span><span class="p">,</span>
<span class="na">fetchUrl</span><span class="p">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">url</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">proxy</span> <span class="o">=</span> <span class="nx">config</span><span class="p">.</span><span class="nx">APP</span><span class="p">.</span><span class="nx">API_HOST_PROXY</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">host</span> <span class="o">=</span> <span class="nx">config</span><span class="p">.</span><span class="nx">APP</span><span class="p">.</span><span class="nx">API_HOST</span><span class="p">;</span>
<span class="k">if </span><span class="p">(</span><span class="nx">proxy</span> <span class="o">&&</span> <span class="nx">host</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">url</span> <span class="o">=</span> <span class="nx">url</span><span class="p">.</span><span class="nf">replace</span><span class="p">(</span><span class="nx">proxy</span><span class="p">,</span> <span class="nx">host</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">return</span> <span class="nx">url</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">});</span>
</code></pre>
<p>Well I need to define at least one URL for the service, from there I can use the relationships' links' objects.</p>
<p>Perhaps the methods my <code>Adapter</code> needs are: <code>find</code>, <code>findOne</code>, <code>findQuery</code>, <code>findRelated</code>, <code>fetch</code>, <code>createResource</code>, <code>deleteResource</code>, <code>updateResource</code>, <code>patchRelationship</code>, and <code>cacheResource</code>. And a couple properties: <code>type</code> and <code>url</code>. That should make for solid interface to speak JSON over HTTP.</p>
<p>Now that I have an <code>Adapter</code> I'll need a <code>Serializer</code> object to serialize/deserialize resources, basically I need to make <code>Resource</code> instances after I <code>fetch</code> and will need to serialize changed attributes to patch the resource to meet my goal of persisting changes as close to real time as possible.</p>
<h3>A Serializer Object</h3>
<p>Well I think since I've followed a solid specification, JSON API, the needs for this prototype will be simple.</p>
<p>Perhaps the interface will include methods to: <code>deserialize</code>, <code>deserializeIncluded</code>, <code>deserializeResource</code>, <code>deserializeResources</code>, <code>serialize</code>, <code>serializeChanged</code>, <code>serializeResource</code>.</p>
<p>I may use a private method for how the resources are created perhaps like:</p>
<pre class="highlight"><code><span class="cm">/**
Create a Resource from a JSON API Resource Object
See <http://jsonapi.org/format/#document-resource-objects>
@private
@method _createResourceInstance
@param {Object} json
@return {Resource} instance
*/</span>
<span class="nf">_createResourceInstance</span><span class="p">(</span><span class="nx">json</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">factoryName</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">model:</span><span class="dl">'</span> <span class="o">+</span> <span class="nx">json</span><span class="p">.</span><span class="nx">type</span><span class="p">;</span>
<span class="nx">json</span><span class="p">.</span><span class="nx">meta</span> <span class="o">=</span> <span class="nx">json</span><span class="p">.</span><span class="nx">meta</span> <span class="o">||</span> <span class="p">{};</span>
<span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">container</span><span class="p">.</span><span class="nf">lookupFactory</span><span class="p">(</span><span class="nx">factoryName</span><span class="p">).</span><span class="nf">create</span><span class="p">({</span>
<span class="dl">'</span><span class="s1">type</span><span class="dl">'</span><span class="p">:</span> <span class="nx">json</span><span class="p">.</span><span class="nx">type</span><span class="p">,</span>
<span class="dl">'</span><span class="s1">id</span><span class="dl">'</span><span class="p">:</span> <span class="nx">json</span><span class="p">.</span><span class="nx">id</span><span class="p">,</span>
<span class="dl">'</span><span class="s1">attributes</span><span class="dl">'</span><span class="p">:</span> <span class="nx">json</span><span class="p">.</span><span class="nx">attributes</span><span class="p">,</span>
<span class="dl">'</span><span class="s1">relationships</span><span class="dl">'</span><span class="p">:</span> <span class="nx">json</span><span class="p">.</span><span class="nx">relationships</span><span class="p">,</span>
<span class="dl">'</span><span class="s1">links</span><span class="dl">'</span><span class="p">:</span> <span class="nx">json</span><span class="p">.</span><span class="nx">links</span><span class="p">,</span>
<span class="dl">'</span><span class="s1">meta</span><span class="dl">'</span><span class="p">:</span> <span class="nx">json</span><span class="p">.</span><span class="nx">meta</span>
<span class="p">});</span>
<span class="p">}</span>
</code></pre>
<p>Like I said earlier the <code>Resource</code> should closely resemble the specification. No need to change it around and confuse things for developers. </p>
<p>Well this may be different that what people are used to, but the JSON API spec is sweet - so going with that. Notice that the properties created with the <code>attr()</code> helper are not used during <code>.create()</code> all the attributes belong in the <code>attributes</code> object, period; that sould be protected, that's how the spec defines attributes.</p>
<p>Using an initializer, I can register this factory in the application container and inject a singleton instance as dependency into the <code>Adapter</code> singleton. Oh, did I mention that I'd register and inject the <code>Adapter</code> factory? Yeah these two work in tandem.</p>
<p>Please be patient my friend, I'll get to all the details about initialization soon. I think I'm still missing a couple pieces, like a strategy for caching.</p>
<h3>A Cache Mixin</h3>
<p>Well now this is a can of worms. I thought HTTP and browsers already solved this problem. Yes, and now is my opportunity to get out of the business of managing a distributed cache by blindly pushing everything into memory and dancing around that.</p>
<p>Managing a distributed cache requires flexibility. When a client application receives a representation of a <code>resource</code> from a server, the client should be able to expire that cached object (in the browser application) based on the server's response headers; or by whatever means the developer chooses.</p>
<p>Perhaps the caching plan for a service is simply creating a <code>ServiceCacheMixin</code> that can be easily customized and has some basic defaults. I recall that Akamai defaults to about 7 minutes, or at least when I used it that was our default. How about that, I'll setup a property for the <code>Resource</code> prototype with an expires time e.g. <code>cacheDuration</code> and a computed boolean for <code>isCacheExpired</code>.</p>
<p>The cache mixin will need a property to hold it's contents, e.g. <code>cache</code> and some methods like: <code>cacheControl</code>, <code>cacheData</code>, <code>cacheLookup</code>, <code>cacheMeta</code>, <code>cacheResource</code>, <code>cacheUpdate</code>. </p>
<p>The <code>cacheControl</code> method can track the response headers from the request and mark some timestamps like the server time and the client time, for our simple expire in X minutes approach.</p>
<p>Why would I want to cache the data in memory anyway? I mentioned that was a solved problem, no?</p>
<p>I want to fetch a resource that <code>hasMany</code> other resources and in my template will reference that like <code>resource.comments</code> so that the related resources are fetched (asynchronously and on demand). Those related resources may have a common relation, let's call it a <em>commenter</em>. For example a <em>post</em> has a bunch of <em>comments</em>, cause you know it was popular. An there was some dialog so a few people commented a fews times in the discussion. Well I don't want to fetch those same <em>commenters</em> over and over do I? I don't mind the option to cache, but also like the option not to. To cache or not to cache that is a question for the server and perhaps the developer. That's where the default expires time comes in handy. As the developer I can set the rules for how long a resource is temporarily cached in memory and when I just want to trust the cache headers and lean on 304 responses.</p>
<p>If the caching strategy is simply a mixin it's a great hook to do something more advanced like using the <code>cacheData</code>, <code>cacheMeta</code> and <code>cacheLookup</code> to keep offline copies in localStorage if I want to be 'offline ready'.</p>
<p>Hum, so now that I have an adapter, a serializer and a cache mixin it seems like that would be a good recipe for a <code>Service</code> object to manage the combined solution for data persistence. Perhaps so…</p>
<h3>A Service Object</h3>
<p>A service for a resource can work to persist an instance of that entity using crud operations with HTTP verbs: CREATE, PATCH, GET, DELETE. The service can be named after the nouns of my JSON API server. For example a post resource (entity) may be requested using GET <code>/api/v1/posts</code> or GET <code>/api/v1/posts/1</code>. A <code>service</code> instance can be a singleton that is registered on my application's container using a pluralized name for the entity, e.g. 'posts'. Again we'll need to use an initializer to put this service into play and may also inject the service at will, for example on the resource prototypes using <code>Ember.inject.service('posts')</code> </p>
<p>Perhaps <code>Service</code> objects are "evented" to facilitate close to real-time updates.</p>
<p>When an <code>attr</code> is <code>set</code> and the value has changed the resource may emit an event for <code>attributeChanged</code> on the resource's <code>service</code> object. The adapter can listen to this event and respond with a call to <code>updateResource</code> which in turn sends a PATCH request with only the data for the changed attributes (a payload created by the serializer's <code>serializeChanged</code> method).</p>
<p>That would be a good way for the collaboraters, the fantastic four (adapter, serializer, cache and resources), to form as a <code>service</code> to act out the requirements for the application's data persistence needs.</p>
<p>A <code>PostsService</code> object may be as simple as this:</p>
<pre class="highlight"><code><span class="k">import</span> <span class="nx">Adapter</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../adapters/post</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="nx">ServiceCache</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../mixins/service-cache</span><span class="dl">'</span><span class="p">;</span>
<span class="nx">Adapter</span><span class="p">.</span><span class="nf">reopenClass</span><span class="p">({</span> <span class="na">isServiceFactory</span><span class="p">:</span> <span class="kc">true</span> <span class="p">});</span>
<span class="k">export</span> <span class="k">default</span> <span class="nx">Adapter</span><span class="p">.</span><span class="nf">extend</span><span class="p">(</span><span class="nx">ServiceCache</span><span class="p">);</span>
</code></pre>
<p>With the injection of this service into the <code>Resource</code> instances there is an event bus for these collaborators to work.</p>
<p>Now that these four prototypes have taken shape let's get into the business of dependency injection using an initializer.</p>
<h3>The Resource Blueprint</h3>
<p>Since I am building an Ember CLI addon for this data persistence solution I can create <a href="proxy.php?url=http://www.ember-cli.com/#developing-addons-and-blueprints" target="_blank" rel="noopener noreferrer">blueprints</a> for all the things.</p>
<p>The <code>resource</code> blueprint is a generator for an initializer, a resource, an adapter, a serializer, and a service - all created by using the command <code>ember generate resource post</code></p>
<p>So here is what the initializer can look like to set all these objects into motion:</p>
<pre class="highlight"><code><span class="k">import</span> <span class="nx">Service</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../services/posts</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="nx">Model</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../models/post</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="nx">Adapter</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../adapters/post</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="nx">Serializer</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../serializers/post</span><span class="dl">'</span><span class="p">;</span>
<span class="k">export</span> <span class="kd">function</span> <span class="nf">initialize</span><span class="p">(</span><span class="nx">container</span><span class="p">,</span> <span class="nx">application</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">const</span> <span class="nx">adapter</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">service:posts-adapter</span><span class="dl">'</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">serializer</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">service:posts-serializer</span><span class="dl">'</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">service</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">service:posts</span><span class="dl">'</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">model</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">model:posts</span><span class="dl">'</span><span class="p">;</span>
<span class="nx">application</span><span class="p">.</span><span class="nf">register</span><span class="p">(</span><span class="nx">model</span><span class="p">,</span> <span class="nx">Model</span><span class="p">,</span> <span class="p">{</span> <span class="na">instantiate</span><span class="p">:</span> <span class="kc">false</span> <span class="p">});</span>
<span class="nx">application</span><span class="p">.</span><span class="nf">register</span><span class="p">(</span><span class="nx">service</span><span class="p">,</span> <span class="nx">Service</span><span class="p">);</span>
<span class="nx">application</span><span class="p">.</span><span class="nf">register</span><span class="p">(</span><span class="nx">adapter</span><span class="p">,</span> <span class="nx">Adapter</span><span class="p">);</span>
<span class="nx">application</span><span class="p">.</span><span class="nf">register</span><span class="p">(</span><span class="nx">serializer</span><span class="p">,</span> <span class="nx">Serializer</span><span class="p">);</span>
<span class="nx">application</span><span class="p">.</span><span class="nf">inject</span><span class="p">(</span><span class="nx">model</span><span class="p">,</span> <span class="dl">'</span><span class="s1">service</span><span class="dl">'</span><span class="p">,</span> <span class="nx">service</span><span class="p">);</span>
<span class="nx">application</span><span class="p">.</span><span class="nf">inject</span><span class="p">(</span><span class="dl">'</span><span class="s1">service:store</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">posts</span><span class="dl">'</span><span class="p">,</span> <span class="nx">service</span><span class="p">);</span>
<span class="nx">application</span><span class="p">.</span><span class="nf">inject</span><span class="p">(</span><span class="nx">service</span><span class="p">,</span> <span class="dl">'</span><span class="s1">serializer</span><span class="dl">'</span><span class="p">,</span> <span class="nx">serializer</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">export</span> <span class="k">default</span> <span class="p">{</span>
<span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">posts-service</span><span class="dl">'</span><span class="p">,</span>
<span class="na">after</span><span class="p">:</span> <span class="dl">'</span><span class="s1">store</span><span class="dl">'</span><span class="p">,</span>
<span class="na">initialize</span><span class="p">:</span> <span class="nx">initialize</span>
<span class="p">};</span>
</code></pre>
<p>Did you notice that, this initializer executes after the <code>store</code> initializer, but I did not create a store yet. Oops, I sould do that. Developers like having a <code>store</code>. </p>
<p>“Let’s put a smile on that face!” -Heath Ledger’s Joker (The Dark Knight)</p>
<h3>The Store Service Object</h3>
<p>A <code>store</code> service can be injected into the routes. This is similar to how <a href="proxy.php?url=https://github.com/emberjs/data" target="_blank" rel="noopener noreferrer">Ember Data</a> uses a <code>store</code>, but the <code>resource</code> should be referenced in the plural form (like my API endpoint), easy peasy.</p>
<p>The interface for a <code>store</code> service can be a simple facade for the services created for each <code>resource</code> in the application. I can call the <code>store</code> method and pass in the <code>resource</code> name, e.g. 'post' which interacts with the 'posts' service for my 'post' <code>resource</code>.</p>
<p>An example <code>route</code> hook to find all post resources from the <code>posts</code> service:</p>
<pre class="highlight"><code><span class="k">import</span> <span class="nx">Ember</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">ember</span><span class="dl">'</span><span class="p">;</span>
<span class="k">export</span> <span class="k">default</span> <span class="nx">Ember</span><span class="p">.</span><span class="nx">Route</span><span class="p">.</span><span class="nf">extend</span><span class="p">({</span>
<span class="nf">model</span><span class="p">()</span> <span class="p">{</span>
<span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">store</span><span class="p">.</span><span class="nf">find</span><span class="p">(</span><span class="dl">'</span><span class="s1">post</span><span class="dl">'</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">});</span>
</code></pre>
<p>This <code>Store</code> object is a facade to the various other <code>Service</code> objects in the application. Each service will be injected into the to store service. So it can be a service of services. One interface for all my persistance needs.</p>
<p>In the example above, the store object can pluralize the type, to lookup the resource's service object, i.e. 'service:posts'.</p>
<p>Wait, isn't that how Ember's Route#model hook works anyway? Yeah basically, <code>this.store.find('post', '1')</code> or I can use a query object: <code>this.store.find('post', {sort: 'desc', filter: 'blah'})</code>;</p>
<p>I'm not sure if you think this solution would be a lot of work or not. I would work toward create a solid test suite as well. Perhaps that can be a follow up post - <em>on testing a data persistence solution.</em> Now that Ember CLI makes unit testing simple and easy to embrace testing should be fairly straight forward to achieve. Well in addition to the tests there is a dummy application that can be built as demo; even utilized for my manual testing needs - like using a proxy to a live JSON API server. That's what I would do, test all the things, both automated unit tests and manual using an app.</p>
<p><strong>(Spoiler alert)</strong> This app is running with a JSON API server and this data persistence solution – so here is your demo and proof of concept.</p>
<h2>Take Aways</h2>
<p>I recommend getting to know the JSON API 1.0 Spec. I propose that your data persistence solution can be a direct line between your client and server while still utilizing a robust feature set which is inline to accompany the way your application server defines and operates on resources.</p>
<p>Whether you adopt the JSON API 1.0 spec or not… this example (addon) can be a template for creating a data persistence solution for your Ember.js application – which models the domain of your API server without any extra abstraction.</p>
<p>One of the primary drivers for keeping my data layer thin is this concept <strong>e = mc<sup>2</sup></strong>, that is <strong>Errors = (More Code)<sup>2</sup></strong>. I'd like less code and only code that fits my use cases.</p>
<h4>Start Today</h4>
<p>I hope you caught on, in this post I used a verb tense as if I will need to build out this solution. But actually, I already have. Here is a review on <a href="proxy.php?url=http://emberobserver.com/addons/ember-jsonapi-resources" target="_blank" rel="noopener noreferrer">emberobserver.com</a></p>
<ul>
<li><a href="proxy.php?url=https://github.com/pixelhandler/ember-jsonapi-resources/blob/master/README.md" target="_blank" rel="noopener noreferrer">The README</a></li>
<li><a href="proxy.php?url=https://github.com/pixelhandler/jr-test" target="_blank" rel="noopener noreferrer">Demo App</a> (this blog is also a demo of using the solutions linked below)</li>
<li><a href="proxy.php?url=https://github.com/pixelhandler/ember-jsonapi-resources/wiki" target="_blank" rel="noopener noreferrer">Developer's Guide</a></li>
<li><a href="proxy.php?url=http://pixelhandler.github.io/ember-jsonapi-resources/docs/" target="_blank" rel="noopener noreferrer">Code Documentation</a></li>
<li><a href="proxy.php?url=https://travis-ci.org/pixelhandler/ember-jsonapi-resources/builds" target="_blank" rel="noopener noreferrer">Travis builds</a></li>
</ul>
<h4>JSON API Implementations</h4>
<ul>
<li><a href="proxy.php?url=http://jsonapi.org/implementations/" target="_blank" rel="noopener noreferrer">JSON API implementations</a></li>
<li>Ember CLI Addon - <a href="proxy.php?url=https://github.com/pixelhandler/ember-jsonapi-resources" target="_blank" rel="noopener noreferrer">Ember JSONAPI Resources</a></li>
<li>Ruby gem - <a href="proxy.php?url=https://github.com/cerebris/jsonapi-resources" target="_blank" rel="noopener noreferrer">JSONAPI::Resources</a></li>
</ul>
Practical: Deploy an Ember App with ember-cli-deployhttps://pixelhandler.dev/posts/practical-deploy-an-ember-app-with-ember-cli-deploy-on-digitalocean2015-04-25T00:00:00+00:002015-04-25T00:00:00+00:00Billy HeatonFor the application's asset files (CSS, JavaScript, images, etc) this workflow pushes
them to an S3 bucket.<h2>Lightning-Approach Workflow</h2>
<p>This approach is the default setup when using ember-cli-deploy and uses a Redis<br>
store for the versions of your index.html file that you deploy. This strategy allows<br>
for (pre)viewing any deployed version using an URL parameter.</p>
<p>For the application's asset files (CSS, JavaScript, images, etc) this workflow pushes<br>
them to an S3 bucket.</p>
<p>Below is the documentation and includes a diagram of the approach:</p>
<ul>
<li>See <a href="proxy.php?url=https://github.com/ember-cli/ember-cli-deploy#lightning-approach-workflow" target="_blank" rel="noopener noreferrer">Documentation</a></li>
</ul>
<h2>Deploy an App with ember-cli-deploy</h2>
<p><a href="proxy.php?url=https://github.com/ember-cli/ember-cli-deploy#how-to-use" target="_blank" rel="noopener noreferrer">How to use</a>…</p>
<pre class="highlight"><code>ember deploy --environment production
ember deploy:list --environment production
</code></pre>
<p>Pick a version to deploy</p>
<pre class="highlight"><code>ember deploy:activate --revision chat-app:XXXXXX --environment production
</code></pre>
<p>You can preview a version using the <code>index_key</code> parameter, like so<br>
<a href="proxy.php?url=http://chat.pixelhandler.com/?index_key=cde6f62" target="_blank" rel="noopener noreferrer">http://chat.pixelhandler.com/?index_key=cde6f62</a> where the parameter represents a<br>
commit hash that has been deployed (but may not be activated yet).</p>
<h2>Firebase</h2>
<p>Create your own firebase account, <a href="proxy.php?url=https://www.firebase.com/signup/" target="_blank" rel="noopener noreferrer">signup here</a>.<br>
Then your app will have a real-time backend. In your <a href="proxy.php?url=https://github.com/Ember-SC/ember-cli-screencast/blob/master/config/environment.js#L9" target="_blank" rel="noopener noreferrer">app config</a> use your account url:</p>
<ul>
<li>Mine is: <code>https://pixelhandler.firebaseio.com/</code></li>
<li>I set the above URL in the config/environment.js file of my Ember app</li>
</ul>
<h3>Security & Rules</h3>
<p>In your Firebase Application Dashboard, setup the rules for your data. The collection<br>
that the application uses is <code>messages</code>.</p>
<pre class="highlight"><code>{
"rules": {
".read": true,
".write":false,
"messages":{
".read": true,
".write": true,
".indexOn": "timestamp"
}
}
}
</code></pre>
<h2>Setup an Ubuntu box with DigitalOcean</h2>
<p><a href="proxy.php?url=https://www.digitalocean.com/?refcode=906d4e66b348" target="_blank" rel="noopener noreferrer">DigitalOcean</a> has excellent guides on using their hosting service. See this guide<br>
which details the setup of an Ubuntu box:</p>
<ul>
<li>Guide: <a href="proxy.php?url=https://www.digitalocean.com/community/tutorials/initial-server-setup-with-ubuntu-14-04" target="_blank" rel="noopener noreferrer">Initial Server Setup</a></li>
</ul>
<h2>Install Node.js</h2>
<p>I recommend using <a href="proxy.php?url=https://github.com/creationix/nvm" target="_blank" rel="noopener noreferrer">NVM</a>, Node Version Managager, to install versions of Node.js</p>
<ul>
<li>See <a href="proxy.php?url=https://github.com/creationix/nvm" target="_blank" rel="noopener noreferrer">NVM</a></li>
<li>Use <a href="proxy.php?url=https://github.com/foreverjs/forever" target="_blank" rel="noopener noreferrer">forever</a> to continuously run a Node.js app</li>
</ul>
<h2>Install Nginx</h2>
<ul>
<li>Guide: <a href="proxy.php?url=https://www.digitalocean.com/community/tutorials/how-to-install-nginx-on-ubuntu-14-04-lts" target="_blank" rel="noopener noreferrer">how-to-install</a></li>
</ul>
<p>You may need to edit your <a href="proxy.php?url=http://nginx.org/en/" target="_blank" rel="noopener noreferrer">nginx</a> config file or perhaps view the logs…</p>
<h3>Config</h3>
<pre class="highlight"><code><span class="nb">sudo </span>vim /etc/nginx/nginx.conf
</code></pre>
<h3>Logs</h3>
<pre class="highlight"><code><span class="nb">tail</span> <span class="nt">-f</span> /var/log/nginx/access.log
<span class="nb">sudo tail</span> <span class="nt">-f</span> /var/log/nginx/error.log
</code></pre>
<h3>Restart</h3>
<pre class="highlight"><code><span class="nb">sudo </span>service nginx restart
</code></pre>
<h2>Install Redis</h2>
<ul>
<li>Guide: <a href="proxy.php?url=https://www.digitalocean.com/community/tutorials/how-to-install-and-use-redis" target="_blank" rel="noopener noreferrer">how-to-install</a></li>
</ul>
<p>See the <a href="proxy.php?url=http://redis.io" target="_blank" rel="noopener noreferrer">Redis</a> downloads page for the current version:</p>
<ul>
<li><a href="proxy.php?url=http://redis.io/download" target="_blank" rel="noopener noreferrer">http://redis.io/download</a></li>
</ul>
<h3>Post Install Setup</h3>
<p>Setup your redis server with a script…</p>
<pre class="highlight"><code><span class="nb">cd</span> /usr/local/src/redis-3.0.0/utils
<span class="nb">sudo</span> ./install_server.sh
</code></pre>
<p>I used port 6379 which setups up a config file at /etc/redis/6379.conf</p>
<h3>Config</h3>
<p>Bind your IP address for the server your app runs on, mine is on <code>107.170.232.223</code>.</p>
<pre class="highlight"><code>vim /etc/redis/6379.conf
bind 107.170.232.223 127.0.0.1
requirepass yoursecretkeyshouldbeverylongyouknowredisisfastright
</code></pre>
<p>Notice in the config file that you are encouraged to use a long passwords</p>
<h3>Connecting</h3>
<p>I'm using an environment variable for my redis password</p>
<pre class="highlight"><code>redis-cli -h pixelhandler.com -p 6379 -a $REDIS_SECRET
</code></pre>
<p>Or locally on the Ubuntu box:</p>
<pre class="highlight"><code>redis-cli -h 127.0.0.1 -p 6379
</code></pre>
<p>Then enter the <code>AUTH</code> command followed by your secret key</p>
<h3>Start/Stop</h3>
<pre class="highlight"><code><span class="nb">sudo </span>service redis_6379 start
<span class="nb">sudo </span>service redis_6379 stop
</code></pre>
<h3>Show the keys</h3>
<p>Once you deploy a version you can connect then list the keys that are stored:</p>
<pre class="highlight"><code>keys *
</code></pre>
<h3>Install Git</h3>
<p>If not already installed setup your box to use <a href="proxy.php?url=http://git-scm.com" target="_blank" rel="noopener noreferrer">git</a>:</p>
<ul>
<li>Guide: <a href="proxy.php?url=https://www.digitalocean.com/community/tutorials/how-to-install-git-on-ubuntu-14-04" target="_blank" rel="noopener noreferrer">how-to-install</a></li>
</ul>
<h2>AWS S3 Bucket</h2>
<p>I created a bucket named <code>chat-app</code> to host the application assets: the <code>app</code> JS & CSS as well ass the contents of the <code>public</code> directory of the client application.</p>
<ul>
<li>My Endpoint: chat-app.s3-website-us-east-1.amazonaws.com</li>
</ul>
<h3>Bucket Policy</h3>
<p>I used the policy generator tool in AWS to setup a bucket policy for any<br>
Web visitor to read the assets in the bucket for the chat-app:</p>
<pre class="highlight"><code>{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowPublicRead",
"Action": [
"s3:GetObject"
],
"Effect": "Allow",
"Resource": "arn:aws:s3:::chat-app/*",
"Principal": {
"AWS": "*"
}
}
]
}
</code></pre>
<p>I tested an upload of a simple index.html file to be sure the setup works, which will<br>
get clobbered by the deployment anyway. The index.html file that gets pushed to the<br>
bucket will not be used to serve the website.</p>
<h3>Bucket User</h3>
<p>This is a good tutorial on configuring your S3 bucket for deploying<br>
assets for your Ember application:</p>
<ul>
<li>Example setup: <a href="proxy.php?url=http://kerrygallagher.co.uk/deploying-an-ember-cli-application-to-amazon-s3/" target="_blank" rel="noopener noreferrer">deploy to s3</a></li>
</ul>
<p>You'll need to setup an user that can write to your bucket, use the AWS console tools<br>
to create new user for your S3 bucket:</p>
<ul>
<li>IAM policy, services -> IAM -> Create New Users
<ul>
<li><a href="proxy.php?url=https://console.aws.amazon.com/iam/home#users" target="_blank" rel="noopener noreferrer">AWS users</a></li>
</ul></li>
</ul>
<p>You'll need to copy the access key and secret access key to use in your deployment<br>
config for pushing assets to your S3 bucket.</p>
<p>User's <em>Inline Policy</em> for the bucket, notice that the bucket name is the same as<br>
my app name:</p>
<pre class="highlight"><code>{
"Statement": [
{
"Action": "s3:*",
"Effect": "Allow",
"Resource": [
"arn:aws:s3:::chat-app",
"arn:aws:s3:::chat-app/*"
]
}
]
}
</code></pre>
<h3>Environment Variables</h3>
<p>The chat app's deployment script will use environment variables for:</p>
<ul>
<li><code>AWS_ACCESS_KEY</code>, <code>AWS_SECRET_ACCESS_KEY</code></li>
<li>See <a href="proxy.php?url=https://console.aws.amazon.com/iam/home#users" target="_blank" rel="noopener noreferrer">AWS users</a> for your user's access keys</li>
</ul>
<h2>Chat App</h2>
<ul>
<li>Running at <a href="proxy.php?url=http://chat.pixelhandler.com/" target="_blank" rel="noopener noreferrer">http://chat.pixelhandler.com/</a></li>
<li>Source for server app: <a href="proxy.php?url=https://github.com/Ember-SC/ember-lightning" target="_blank" rel="noopener noreferrer">ember-lightning</a></li>
<li>Source for client app: <a href="proxy.php?url=https://github.com/Ember-SC/ember-cli-screencast" target="_blank" rel="noopener noreferrer">ember-cli-screencast</a></li>
</ul>
<p>Server app needs config setup in an <code>app.json</code> file, copy the <a href="proxy.php?url=https://github.com/Ember-SC/ember-lightning/blob/master/app.example.json" target="_blank" rel="noopener noreferrer">app.example.json</a><br>
on your box.</p>
<p>Client app is using environment variables in the config/deploy.js file, I created a dot file that I source when running the <code>ember deploy</code> command. See deploy section above, which lists out the deployment commands.</p>
<p>Clone the example app and install the dependencies:</p>
<pre class="highlight"><code>git clone [email protected]:Ember-SC/ember-cli-screencast.git ./chat-app-client
<span class="nb">cd </span>chat-app-client
npm <span class="nb">install
</span>bower <span class="nb">install</span>
</code></pre>
<p>Try out <code>ember server</code> locally and visit <a href="proxy.php?url=http://localhost:4200" target="_blank" rel="noopener noreferrer">http://localhost:4200</a></p>
<p>If the app works get ready to deploy to your production box.</p>
<p>First setup your server with Node.js & Redis to try out your first deployment with<br>
ember-cli-deploy. The following section explains the hosting setup for your<br>
production server…</p>
<h3>Vhost Setup</h3>
<p>Using nginx I setup a proxy to the port that the node application will run on.</p>
<ul>
<li>See: <a href="proxy.php?url=https://www.digitalocean.com/community/tutorials/how-to-host-multiple-node-js-applications-on-a-single-vps-with-nginx-forever-and-crontab" target="_blank" rel="noopener noreferrer">host multiple node.js apps</a></li>
</ul>
<h4>DNS</h4>
<p>I used Amazon's Route 53 product for my DNS. So I added a subdomain on<br>
my hosted zone:</p>
<pre class="highlight"><code>chat.pixelhandler.com.
A
107.170.232.223
</code></pre>
<h4>Serve up the Index.html page</h4>
<p>When using ember-cli-deploy-redis, versions of your app will have a key that<br>
is a combination of your app name and the commit hash of the release.<br>
Your Redis DB will store the various deployed versions of your index page.</p>
<p>See <a href="proxy.php?url=https://github.com/ember-cli/ember-cli-deploy#existing-custom-adapters" target="_blank" rel="noopener noreferrer">ember-cli-deploy adapters</a> for existing index adapters</p>
<p>I setup a <code>www</code> directory in my home user, then used git to clone the<br>
example [ember-lightning] app which connects to redis and serves up the<br>
<a href="proxy.php?url=https://github.com/Ember-SC/ember-lightning/blob/master/index.js" target="_blank" rel="noopener noreferrer">index.html page in a node.js app</a>. The app also accepts an URL<br>
query parameter to get the version of the index.html to send back to the browser.</p>
<p>See: <a href="proxy.php?url=https://github.com/Ember-SC/ember-lightning" target="_blank" rel="noopener noreferrer">ember-lightning</a></p>
<pre class="highlight"><code><span class="nb">cd</span> ~/www/
git clone https://github.com/Ember-SC/ember-lightning ./chat-app-server
<span class="nb">cd</span> ./chat-app-server
</code></pre>
<p>Run with forever…</p>
<pre class="highlight"><code>nvm use 0.12.2
npm <span class="nb">install
</span>forever start <span class="nt">-c</span> <span class="s2">"node --harmony"</span> <span class="nt">-o</span> ~/www/chat-app-server/out.log <span class="nt">-e</span> ~/www/chat-app-server/err.log <span class="nt">--spinSleepTime</span> 10000 <span class="nt">--minUptime</span> 10000 ~/www/chat-app-server/index.js <span class="o">>></span> ~/www/chat-app-server/out.log 2>&1
</code></pre>
<h4>Nginx config for the chat-app</h4>
<p>Create a conf file for your vhost, e.g. <code>/etc/nginx/conf.d/chat.pixelhandler.com.conf</code></p>
<p>This uses a proxy pass to the IP and port that the node.js app runs on. I'm running<br>
on port 8787.</p>
<pre class="highlight"><code><span class="k">server</span> <span class="p">{</span>
<span class="kn">listen</span> <span class="mi">80</span><span class="p">;</span>
<span class="kn">server_name</span> <span class="s">chat.pixelhandler.com</span><span class="p">;</span>
<span class="kn">location</span> <span class="n">/</span> <span class="p">{</span>
<span class="kn">proxy_pass</span> <span class="s">http://107.170.232.223:8787</span><span class="p">;</span>
<span class="kn">proxy_http_version</span> <span class="mf">1.1</span><span class="p">;</span>
<span class="kn">proxy_set_header</span> <span class="s">Upgrade</span> <span class="nv">$http_upgrade</span><span class="p">;</span>
<span class="kn">proxy_set_header</span> <span class="s">Connection</span> <span class="s">'upgrade'</span><span class="p">;</span>
<span class="kn">proxy_set_header</span> <span class="s">Host</span> <span class="nv">$host</span><span class="p">;</span>
<span class="kn">proxy_cache_bypass</span> <span class="nv">$http_upgrade</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre>
<p>Restart nginx</p>
<pre class="highlight"><code><span class="nb">sudo </span>service nginx restart
</code></pre>
<h2>Recap</h2>
<p>I used an Ubuntu box to deploy a client Web applicaiton that uses Firebase as a<br>
backend. These notes provide an exercise in learning to deploy an Ember.js<br>
application built with Ember CLI.</p>
<ul>
<li>See the <a href="proxy.php?url=https://www.firebase.com/blog/2015-03-13-ember-cli-in-9-minutes.html" target="_blank" rel="noopener noreferrer">screencast</a> on building the chat-app by <a href="proxy.php?url=https://github.com/sararob" target="_blank" rel="noopener noreferrer">Sara Robinson</a> of <a href="proxy.php?url=https://www.firebase.com" target="_blank" rel="noopener noreferrer">Firebase</a></li>
<li>My <a href="proxy.php?url=https://github.com/Ember-SC/ember-cli-screencast" target="_blank" rel="noopener noreferrer">fork of the source code</a></li>
<li>The server app the renders the current version and allows previewing
other deployed versions: <a href="proxy.php?url=https://github.com/Ember-SC/ember-lightning" target="_blank" rel="noopener noreferrer">ember-lightning fork</a></li>
</ul>
<p>I chose a Node.js app to serve up the index.html page in anticipation of<br>
the <a href="proxy.php?url=https://github.com/tildeio/ember-cli-fastboot#ember-fastboot" target="_blank" rel="noopener noreferrer">FastBoot</a> addon to render an <a href="proxy.php?url=http://emberjs.com" target="_blank" rel="noopener noreferrer">Ember.js</a> app on the server.</p>
<p>Consider using my referal link to sign up for <a href="proxy.php?url=https://www.digitalocean.com/?refcode=906d4e66b348" target="_blank" rel="noopener noreferrer">DigitalOcean</a> and try out<br>
ember-cli-deploy yourself.</p>
<p>Some alternatives to setting up your own box are:</p>
<ul>
<li><a href="proxy.php?url=http://redistogo.com" target="_blank" rel="noopener noreferrer">http://redistogo.com</a></li>
<li><a href="proxy.php?url=https://www.heroku.com/" target="_blank" rel="noopener noreferrer">https://www.heroku.com/</a></li>
</ul>
<p>So if you what to chat about this article visit <a href="proxy.php?url=http://chat.pixelhandler.com" target="_blank" rel="noopener noreferrer">chat.pixelhandler.com</a>.</p>
How Much Faster is HTMLBars Than Handlebars?https://pixelhandler.dev/posts/how-much-faster-is-htmlbars-than-handlebars2015-03-08T00:00:00+00:002015-03-08T00:00:00+00:00Billy HeatonWhen I shared the data with Kris Seldon, he asked whether I had normalized the data and calculated the geometric mean, since that would be a proper way to compare benchmarks. I found the document H...<p>When I shared the data with Kris Seldon, he asked whether I had normalized the data and calculated the geometric mean, since that would be a proper way to compare benchmarks. I found the document <a href="proxy.php?url=http://ece.uprm.edu/%7Enayda/Courses/Icom5047F06/Papers/paper4.pdf" target="_blank" rel="noopener noreferrer">How not to lie with statistics, The correct way to summarize benchmark results</a> by Philip J. Fleming and John J. Wallace. So, I wrote a query for my data set that would group the metrics data in an effort to normalize the measurements taken from actual users on various devices and group the measurements into the benchmarks that I could generate some reporting data from.<br>
To itentify the benchmarks I could report on I wrote some filters for the various userAgent strings within my metrics data. I considered the combination of the operating system and the browser, e.g. Safari on Macintosh, or iPad combined with a type of metric, e.g. <code>index_view</code>. Then I compared the Ember versions.</p>
<p>With the normalized set of data, I created a query that would take the latest measurements, up to 100, from the normalized data set and calculated the geopmentric mean for the various benchmarks. I calculated the product of each duration in the normalize set; then raised that value by an exponent that is the reciprocal of the number of factors in that product. (Geometric Mean: multiply the numbers and then take the nth root of the product.) The calculation looked something like this <code>Math.round(Math.pow(2 * 3 * 5 * 8 * 13 * 21, 1/6) * 1000) / 1000</code> (I rounded to the nearest thousandth). As for the normalization of the data, the metrics can be reported by the Ember Version, platform (Macintosh/Windows) and browser (Chrome, Safari, Firefox, iPad, iPhone, Android) for the specific metric type, e.g. <code>index_view</code>, <code>post_view</code> or the <code>metric_table</code> (for the long list of 1,000 items).</p>
<p><em>I'd like to share my measurements and calculations…</em></p>
<h2>Performance Report</h2>
<p>For the upgrade on my site to HTMLBars from Handlebars 1.3…</p>
<p><strong>Across the board, for the benchmarks I am intersted in, it looks like HTMLBars brought a 10-22% gain in rendering speeds</strong></p>
<table>
Index View: Home Page
<thead>
<tr>
<th>
Platform
</th>
<th>
Browser
</th>
<th class="not-for-small-screen">
v1.8 Mean
</th>
<th class="not-for-small-screen">
v1.10 Mean
</th>
<th class="not-for-small-screen">
Difference
</th>
<th>
Gain/Loss %
</th>
</tr>
</thead>
<tbody>
<tr>
<td>
Macintosh
</td>
<td>
Safari
</td>
<td class="not-for-small-screen">
162.343
</td>
<td class="not-for-small-screen">
86.653
</td>
<td class="not-for-small-screen">
75.69
</td>
<td>
47%
</td>
</tr>
<tr>
<td>
Macintosh
</td>
<td>
Chrome
</td>
<td class="not-for-small-screen">
183.794
</td>
<td class="not-for-small-screen">
187.945
</td>
<td class="not-for-small-screen">
-4.151
</td>
<td>
-2%
</td>
</tr>
<tr>
<td>
Windows
</td>
<td>
Chrome
</td>
<td class="not-for-small-screen">
136.289
</td>
<td class="not-for-small-screen">
211.616
</td>
<td class="not-for-small-screen">
-75.327
</td>
<td>
-55%
</td>
</tr>
<tr>
<td>
iOS
</td>
<td>
iPhone
</td>
<td class="not-for-small-screen">
450.503
</td>
<td class="not-for-small-screen">
352.764
</td>
<td class="not-for-small-screen">
97.739
</td>
<td>
22%
</td>
</tr>
</tbody>
<tr>
<td>Overall</td>
<td class="not-for-small-screen">
206.89
</td>
<td class="not-for-small-screen">
186.73
</td>
<td class="not-for-small-screen">
20.16
</td>
<td>
10%
</td>
</tr>
</table>
<p><br><br></p>
<table>
Post View: Article Pages
<thead>
<tr>
<th>
Platform
</th>
<th>
Browser
</th>
<th class="not-for-small-screen">
v1.8 Mean
</th>
<th class="not-for-small-screen">
v1.10 Mean
</th>
<th class="not-for-small-screen">
Difference
</th>
<th>
Gain/Loss %
</th>
</tr>
</thead>
<tbody>
<tr>
<td>
iOS
</td>
<td>
iPad
</td>
<td class="not-for-small-screen">
801.595
</td>
<td class="not-for-small-screen">
375.945
</td>
<td class="not-for-small-screen">
425.65
</td>
<td>
53%
</td>
</tr>
<tr>
<td>
iOS
</td>
<td>
iPhone
</td>
<td class="not-for-small-screen">
348.711
</td>
<td class="not-for-small-screen">
302.05
</td>
<td class="not-for-small-screen">
46.661
</td>
<td>
13%
</td>
</tr>
<tr>
<td>
Android
</td>
<td>
Various
</td>
<td class="not-for-small-screen">
850.716
</td>
<td class="not-for-small-screen">
862.627
</td>
<td class="not-for-small-screen">
-11.911
</td>
<td>
-1%
</td>
</tr>
<tr>
<td>
Macintosh
</td>
<td>
Safari
</td>
<td class="not-for-small-screen">
109.321
</td>
<td class="not-for-small-screen">
117.684
</td>
<td class="not-for-small-screen">
-8.363
</td>
<td>
-8%
</td>
</tr>
<tr>
<td>
Macintosh
</td>
<td>
Chrome
</td>
<td class="not-for-small-screen">
145.244
</td>
<td class="not-for-small-screen">
163.481
</td>
<td class="not-for-small-screen">
-18.237
</td>
<td>
-13%
</td>
</tr>
<tr>
<td>
Windows
</td>
<td>
Chrome
</td>
<td class="not-for-small-screen">
203.063
</td>
<td class="not-for-small-screen">
232.081
</td>
<td class="not-for-small-screen">
-29.018
</td>
<td>
-14%
</td>
</tr>
<tr>
<td>
Macintosh
</td>
<td>
Firefox
</td>
<td class="not-for-small-screen">
147.432
</td>
<td class="not-for-small-screen">
140.634
</td>
<td class="not-for-small-screen">
6.798
</td>
<td>
5%
</td>
</tr>
<tr>
<td>
Windows
</td>
<td>
Firefox
</td>
<td class="not-for-small-screen">
200.542
</td>
<td class="not-for-small-screen">
163.704
</td>
<td class="not-for-small-screen">
36.838
</td>
<td>
18%
</td>
</tr>
</tbody>
<tr>
<td>Overall</td>
<td class="not-for-small-screen">
262.68
</td>
<td class="not-for-small-screen">
237.34
</td>
<td class="not-for-small-screen">
25.34
</td>
<td>
10%
</td>
</tr>
</table>
<p><br><br></p>
<table>
Metrics Table: Long List of 1,000 Records
<thead>
<tr>
<th>
Platform
</th>
<th>
Browser
</th>
<th class="not-for-small-screen">
v1.8 Mean
</th>
<th class="not-for-small-screen">
v1.10 Mean
</th>
<th class="not-for-small-screen">
Difference
</th>
<th>
Gain/Loss %
</th>
</tr>
</thead>
<tbody>
<tr>
<td>
iOS
</td>
<td>
iPad
</td>
<td class="not-for-small-screen">
3323.655
</td>
<td class="not-for-small-screen">
2170.638
</td>
<td class="not-for-small-screen">
1153.017
</td>
<td>
35%
</td>
</tr>
<tr>
<td>
iOS
</td>
<td>
iPhone
</td>
<td class="not-for-small-screen">
1580.537
</td>
<td class="not-for-small-screen">
1268.771
</td>
<td class="not-for-small-screen">
311.766
</td>
<td>
20%
</td>
</tr>
<tr>
<td>
Macintosh
</td>
<td>
Safari
</td>
<td class="not-for-small-screen">
448.611
</td>
<td class="not-for-small-screen">
302.707
</td>
<td class="not-for-small-screen">
145.904
</td>
<td>
33%
</td>
</tr>
<tr>
<td>
Macintosh
</td>
<td>
Chrome
</td>
<td class="not-for-small-screen">
903.33
</td>
<td class="not-for-small-screen">
646.06
</td>
<td class="not-for-small-screen">
257.27
</td>
<td>
28%
</td>
</tr>
<tr>
<td>
Windows
</td>
<td>
Chrome
</td>
<td class="not-for-small-screen">
812.356
</td>
<td class="not-for-small-screen">
829.604
</td>
<td class="not-for-small-screen">
-17.248
</td>
<td>
-2%
</td>
</tr>
<tr>
<td>
Macintosh
</td>
<td>
Firefox
</td>
<td class="not-for-small-screen">
865.249
</td>
<td class="not-for-small-screen">
743.429
</td>
<td class="not-for-small-screen">
121.82
</td>
<td>
14%
</td>
</tr>
</tbody>
<tr>
<td>Overall</td>
<td class="not-for-small-screen">
1069.48
</td>
<td class="not-for-small-screen">
832.20
</td>
<td class="not-for-small-screen">
237.27
</td>
<td>
22%
</td>
</tr>
</table>
<p><br></p>
<h2>The Details</h2>
<p>Below is a look at the calculations for each of the benchmarks that I included in the summary above.</p>
<h3>The Index (Home) Page</h3>
<h4>Safari on Macintosh</h4>
<p><em>HTMLbars shaved of 76 milliseconds, a 46% gain in rendering speed</em></p>
<pre class="highlight"><code><span class="p">{</span><span class="w">
</span><span class="err">fast:</span><span class="w"> </span><span class="mf">32.94</span><span class="p">,</span><span class="w">
</span><span class="err">geometric_mean:</span><span class="w"> </span><span class="mf">86.653</span><span class="p">,</span><span class="w">
</span><span class="err">measurments:</span><span class="w"> </span><span class="mi">100</span><span class="p">,</span><span class="w">
</span><span class="err">name:</span><span class="w"> </span><span class="s2">"index_view"</span><span class="p">,</span><span class="w">
</span><span class="err">slow:</span><span class="w"> </span><span class="mi">819</span><span class="p">,</span><span class="w">
</span><span class="err">emberVersion:</span><span class="w"> </span><span class="s2">"1.10"</span><span class="p">,</span><span class="w">
</span><span class="err">platform:</span><span class="w"> </span><span class="s2">"Macintosh"</span><span class="p">,</span><span class="w">
</span><span class="err">browser:</span><span class="w"> </span><span class="s2">"Safari"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="err">fast:</span><span class="w"> </span><span class="mf">105.18</span><span class="p">,</span><span class="w">
</span><span class="err">geometric_mean:</span><span class="w"> </span><span class="mf">162.343</span><span class="p">,</span><span class="w">
</span><span class="err">measurments:</span><span class="w"> </span><span class="mi">4</span><span class="p">,</span><span class="w">
</span><span class="err">name:</span><span class="w"> </span><span class="s2">"index_view"</span><span class="p">,</span><span class="w">
</span><span class="err">slow:</span><span class="w"> </span><span class="mf">244.669</span><span class="p">,</span><span class="w">
</span><span class="err">emberVersion:</span><span class="w"> </span><span class="s2">"1.8"</span><span class="p">,</span><span class="w">
</span><span class="err">platform:</span><span class="w"> </span><span class="s2">"Macintosh"</span><span class="p">,</span><span class="w">
</span><span class="err">browser:</span><span class="w"> </span><span class="s2">"Safari"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre>
<h4>Chrome on Macintosh</h4>
<p><em>Only a 4 millisecond difference, a 2% decrease in rendering speed</em></p>
<pre class="highlight"><code>{
fast: 65.41,
geometric_mean: 187.945,
measurments: 100,
name: "index_view",
slow: 9547.233,
emberVersion: "1.10",
platform: "Macintosh",
browser: "Chrome"
}
{
fast: 90.804,
geometric_mean: 183.794,
measurments: 70,
name: "index_view",
slow: 550.173,
emberVersion: "1.8",
platform: "Macintosh",
browser: "Chrome"
}
</code></pre>
<h4>Chrome on Windows</h4>
<p><em>Handlebars 1.3 was 74 milliseconds faster, a 35% gain in rendering speed</em></p>
<pre class="highlight"><code>{
fast: 81.023,
geometric_mean: 211.616,
measurments: 100,
name: "index_view",
slow: 1397,
emberVersion: "1.10",
platform: "Windows",
browser: "Chrome"
}
{
fast: 95.374,
geometric_mean: 136.289,
measurments: 13,
name: "index_view",
slow: 172.329,
emberVersion: "1.8",
platform: "Windows",
browser: "Chrome"
}
</code></pre>
<h4>Android</h4>
<p><em>Only had measurements on Ember v1.10.0</em></p>
<pre class="highlight"><code>{
fast: 520.524,
geometric_mean: 841.979,
measurments: 18,
name: "index_view",
slow: 1382.816,
emberVersion: "1.10",
platform: null,
browser: "Android"
}
</code></pre>
<h4>iPad</h4>
<p><em>Only had measurements on Ember v1.10.0</em></p>
<pre class="highlight"><code>{
fast: 155,
geometric_mean: 484.614,
measurments: 34,
name: "index_view",
slow: 1120,
emberVersion: "1.10",
platform: null,
browser: "iPad"
}
</code></pre>
<h4>iPhone</h4>
<p><em>HTMLBars shaved off 98 milliseconds, a 22% gain in rendering speed</em></p>
<pre class="highlight"><code>{
fast: 143,
geometric_mean: 352.764,
measurments: 62,
name: "index_view",
slow: 1490,
emberVersion: "1.10",
platform: null,
browser: "iPhone"
}
{
fast: 330,
geometric_mean: 450.503,
measurments: 7,
name: "index_view",
slow: 588,
emberVersion: "1.8",
platform: null,
browser: "iPhone"
}
</code></pre>
<p>So for the index page the gains from HTMLBars I noticed were on Safari on Macintosh and on the iPhone. However using Chrome on Windows Handlebars was faster.</p>
<p>I didn't have lots of data for the home page in Ember v1.8.1 but did get loads of traffic on some of my posts, let's see how the individual posts pages did.</p>
<h3>Post (Article) Pages</h3>
<h4>iPad</h4>
<p><em>HTMLBars shaved off about 426 milliseconds, a 53% gain in rendering speed</em></p>
<pre class="highlight"><code>{
fast: 116,
geometric_mean: 375.945,
measurments: 100,
name: "post_view",
slow: 2051,
emberVersion: "1.10",
platform: null,
browser: "iPad"
}
{
fast: 193,
geometric_mean: 801.595,
measurments: 18,
name: "post_view",
slow: 1449,
emberVersion: "1.8",
platform: null,
browser: "iPad"
}
</code></pre>
<h4>iPhone</h4>
<p><em>HTMLBars shaved off about 47 milliseconds, an 11% gain in rendering speed</em></p>
<pre class="highlight"><code>{
fast: 8,
geometric_mean: 302.05,
measurments: 100,
name: "post_view",
slow: 2139,
emberVersion: "1.10",
platform: null,
browser: "iPhone"
}
{
fast: 174,
geometric_mean: 348.711,
measurments: 32,
name: "post_view",
slow: 985,
emberVersion: "1.8",
platform: null,
browser: "iPhone"
}
</code></pre>
<h4>Android</h4>
<p><em>Handlebars 1.3 was 12 milliseconds faster, no rendering speed gains from upgrade to HTMLBars</em></p>
<pre class="highlight"><code>{
fast: 204.868,
geometric_mean: 862.627,
measurments: 100,
name: "post_view",
slow: 4610.199,
emberVersion: "1.10",
platform: null,
browser: "Android"
}
{
fast: 275.503,
geometric_mean: 850.716,
measurments: 74,
name: "post_view",
slow: 2747.738,
emberVersion: "1.8",
platform: null,
browser: "Android"
}
</code></pre>
<h4>Safari on Macintosh</h4>
<p><em>Handlebars 1.3 was 8 milliseconds faster, no rendering speed gains from upgrade to HTMLBars</em></p>
<pre class="highlight"><code>{
fast: 20.164,
geometric_mean: 117.684,
measurments: 100,
name: "post_view",
slow: 5616.18,
emberVersion: "1.10",
platform: "Macintosh",
browser: "Safari"
}
{
fast: 50.377,
geometric_mean: 109.321,
measurments: 100,
name: "post_view",
slow: 2551.46,
emberVersion: "1.8",
platform: "Macintosh",
browser: "Safari"
}
</code></pre>
<h4>Chrome on Macintosh</h4>
<p><em>Handlebars 1.3 was 18 milliseconds faster, no rendering speed gains from upgrade to HTMLBars</em></p>
<pre class="highlight"><code>{
fast: 19.82,
geometric_mean: 163.481,
measurments: 100,
name: "post_view",
slow: 4220.102,
emberVersion: "1.10",
platform: "Macintosh",
browser: "Chrome"
}
{
fast: 18.673,
geometric_mean: 145.244,
measurments: 100,
name: "post_view",
slow: 2884.554,
emberVersion: "1.8",
platform: "Macintosh",
browser: "Chrome"
}
</code></pre>
<h4>Chrome on Windows</h4>
<p><em>Handlebars 1.3 was 29 milliseconds faster, no rendering speed gains from upgrade to HTMLBars</em></p>
<pre class="highlight"><code>{
fast: 31.697,
geometric_mean: 232.081,
measurments: 100,
name: "post_view",
slow: 13214,
emberVersion: "1.10",
platform: "Windows",
browser: "Chrome"
}
{
fast: 56.472,
geometric_mean: 203.063,
measurments: 100,
name: "post_view",
slow: 3696,
emberVersion: "1.8",
platform: "Windows",
browser: "Chrome"
}
</code></pre>
<h4>Firefox on Macintosh</h4>
<p><em>HTMLBars was 7 milliseconds faster, only a minor gain in rendering speed</em></p>
<pre class="highlight"><code>{
fast: 45.745,
geometric_mean: 140.634,
measurments: 100,
name: "post_view",
slow: 1247.685,
emberVersion: "1.10",
platform: "Macintosh",
browser: "Firefox"
}
{
fast: 71.173,
geometric_mean: 147.432,
measurments: 62,
name: "post_view",
slow: 387.791,
emberVersion: "1.8",
platform: "Macintosh",
browser: "Firefox"
}
</code></pre>
<h4>Firefox on Windows</h4>
<p><em>HTMLBars was 37 milliseconds faster, an 18% gain in rendering speed</em></p>
<pre class="highlight"><code>{
fast: 73.865,
geometric_mean: 163.704,
measurments: 100,
name: "post_view",
slow: 1860,
emberVersion: "1.10",
platform: "Windows",
browser: "Firefox"
}
{
fast: 69.896,
geometric_mean: 200.542,
measurments: 100,
name: "post_view",
slow: 1305.268,
emberVersion: "1.8",
platform: "Windows",
browser: "Firefox"
}
</code></pre>
<h3>Metrics Table (long list of 1,000 records)</h3>
<h4>iPad</h4>
<p>I only had a couple measurments…</p>
<p><em>Looks like HTMLBars can be 1153 milliseconds faster, a 35% gain in rendering speed</em></p>
<pre class="highlight"><code>{
fast: 2062,
geometric_mean: 2170.638,
measurments: 2,
name: "metrics_table",
slow: 2285,
emberVersion: "1.10",
platform: null,
browser: "iPad"
}
{
fast: 3103,
geometric_mean: 3323.655,
measurments: 2,
name: "metrics_table",
slow: 3560,
emberVersion: "1.8",
platform: null,
browser: "iPad"
}
</code></pre>
<h4>iPhone</h4>
<p><em>HTMLBars was 312 milliseconds faster, a 20% gain in rendering speed</em></p>
<pre class="highlight"><code>{
fast: 675,
geometric_mean: 1268.771,
measurments: 8,
name: "metrics_table",
slow: 1568,
emberVersion: "1.10",
platform: null,
browser: "iPhone"
}
{
fast: 871,
geometric_mean: 1580.537,
measurments: 8,
name: "metrics_table",
slow: 5380,
emberVersion: "1.8",
platform: null,
browser: "iPhone"
}
</code></pre>
<h4>Safari on Macintosh</h4>
<p><em>HTMLBars was 146 milliseconds faster, a 48% gain in rendering speed</em></p>
<pre class="highlight"><code><span class="p">{</span><span class="w">
</span><span class="err">fast:</span><span class="w"> </span><span class="mf">239.835</span><span class="p">,</span><span class="w">
</span><span class="err">geometric_mean:</span><span class="w"> </span><span class="mf">302.707</span><span class="p">,</span><span class="w">
</span><span class="err">measurments:</span><span class="w"> </span><span class="mi">13</span><span class="p">,</span><span class="w">
</span><span class="err">name:</span><span class="w"> </span><span class="s2">"metrics_table"</span><span class="p">,</span><span class="w">
</span><span class="err">slow:</span><span class="w"> </span><span class="mf">416.321</span><span class="p">,</span><span class="w">
</span><span class="err">emberVersion:</span><span class="w"> </span><span class="s2">"1.10"</span><span class="p">,</span><span class="w">
</span><span class="err">platform:</span><span class="w"> </span><span class="s2">"Macintosh"</span><span class="p">,</span><span class="w">
</span><span class="err">browser:</span><span class="w"> </span><span class="s2">"Safari"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="err">fast:</span><span class="w"> </span><span class="mi">287</span><span class="p">,</span><span class="w">
</span><span class="err">geometric_mean:</span><span class="w"> </span><span class="mf">448.611</span><span class="p">,</span><span class="w">
</span><span class="err">measurments:</span><span class="w"> </span><span class="mi">6</span><span class="p">,</span><span class="w">
</span><span class="err">name:</span><span class="w"> </span><span class="s2">"metrics_table"</span><span class="p">,</span><span class="w">
</span><span class="err">slow:</span><span class="w"> </span><span class="mf">568.253</span><span class="p">,</span><span class="w">
</span><span class="err">emberVersion:</span><span class="w"> </span><span class="s2">"1.8"</span><span class="p">,</span><span class="w">
</span><span class="err">platform:</span><span class="w"> </span><span class="s2">"Macintosh"</span><span class="p">,</span><span class="w">
</span><span class="err">browser:</span><span class="w"> </span><span class="s2">"Safari"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre>
<h4>Chrome on Macintosh</h4>
<p><em>HTMLBars was 257 milliseconds faster, a 28% gain in rendering speed</em></p>
<pre class="highlight"><code><span class="p">{</span><span class="w">
</span><span class="err">fast:</span><span class="w"> </span><span class="mf">414.107</span><span class="p">,</span><span class="w">
</span><span class="err">geometric_mean:</span><span class="w"> </span><span class="mf">646.06</span><span class="p">,</span><span class="w">
</span><span class="err">measurments:</span><span class="w"> </span><span class="mi">27</span><span class="p">,</span><span class="w">
</span><span class="err">name:</span><span class="w"> </span><span class="s2">"metrics_table"</span><span class="p">,</span><span class="w">
</span><span class="err">slow:</span><span class="w"> </span><span class="mf">1584.423</span><span class="p">,</span><span class="w">
</span><span class="err">emberVersion:</span><span class="w"> </span><span class="s2">"1.10"</span><span class="p">,</span><span class="w">
</span><span class="err">platform:</span><span class="w"> </span><span class="s2">"Macintosh"</span><span class="p">,</span><span class="w">
</span><span class="err">browser:</span><span class="w"> </span><span class="s2">"Chrome"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="err">fast:</span><span class="w"> </span><span class="mf">457.012</span><span class="p">,</span><span class="w">
</span><span class="err">geometric_mean:</span><span class="w"> </span><span class="mf">903.33</span><span class="p">,</span><span class="w">
</span><span class="err">measurments:</span><span class="w"> </span><span class="mi">27</span><span class="p">,</span><span class="w">
</span><span class="err">name:</span><span class="w"> </span><span class="s2">"metrics_table"</span><span class="p">,</span><span class="w">
</span><span class="err">slow:</span><span class="w"> </span><span class="mf">1564.946</span><span class="p">,</span><span class="w">
</span><span class="err">emberVersion:</span><span class="w"> </span><span class="s2">"1.8"</span><span class="p">,</span><span class="w">
</span><span class="err">platform:</span><span class="w"> </span><span class="s2">"Macintosh"</span><span class="p">,</span><span class="w">
</span><span class="err">browser:</span><span class="w"> </span><span class="s2">"Chrome"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre>
<h4>Chrome on Windows</h4>
<p><em>Handlebars 1.3 was 17 milliseconds faster, a 2% decrease in rendering speed</em></p>
<pre class="highlight"><code><span class="p">{</span><span class="w">
</span><span class="err">fast:</span><span class="w"> </span><span class="mf">615.905</span><span class="p">,</span><span class="w">
</span><span class="err">geometric_mean:</span><span class="w"> </span><span class="mf">829.604</span><span class="p">,</span><span class="w">
</span><span class="err">measurments:</span><span class="w"> </span><span class="mi">3</span><span class="p">,</span><span class="w">
</span><span class="err">name:</span><span class="w"> </span><span class="s2">"metrics_table"</span><span class="p">,</span><span class="w">
</span><span class="err">slow:</span><span class="w"> </span><span class="mf">1142.15</span><span class="p">,</span><span class="w">
</span><span class="err">emberVersion:</span><span class="w"> </span><span class="s2">"1.10"</span><span class="p">,</span><span class="w">
</span><span class="err">platform:</span><span class="w"> </span><span class="s2">"Windows"</span><span class="p">,</span><span class="w">
</span><span class="err">browser:</span><span class="w"> </span><span class="s2">"Chrome"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="err">fast:</span><span class="w"> </span><span class="mf">765.64</span><span class="p">,</span><span class="w">
</span><span class="err">geometric_mean:</span><span class="w"> </span><span class="mf">812.356</span><span class="p">,</span><span class="w">
</span><span class="err">measurments:</span><span class="w"> </span><span class="mi">2</span><span class="p">,</span><span class="w">
</span><span class="err">name:</span><span class="w"> </span><span class="s2">"metrics_table"</span><span class="p">,</span><span class="w">
</span><span class="err">slow:</span><span class="w"> </span><span class="mf">861.923</span><span class="p">,</span><span class="w">
</span><span class="err">emberVersion:</span><span class="w"> </span><span class="s2">"1.8"</span><span class="p">,</span><span class="w">
</span><span class="err">platform:</span><span class="w"> </span><span class="s2">"Windows"</span><span class="p">,</span><span class="w">
</span><span class="err">browser:</span><span class="w"> </span><span class="s2">"Chrome"</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre>
<h4>Firefox on Windows</h4>
<p>I did not collect enough measurements to make a comparison</p>
<h4>Firefox on Macintosh</h4>
<p>The data is limited to 2 and 4 measurements, I've incluced the metrics, and unique userAgents to show the individual measuremnts within the normalized set, for example the Firefox versions include versions 33.0 and 37.0.</p>
<p><em>In this limited set, HTMLBars is 122 milliseconds faster, a 14% gain in rendering speed</em></p>
<pre class="highlight"><code>{
"durations": [
696.713,
903.421,
660.65,
734.586
],
"fast": 660.65,
"geometric_mean": 743.429,
"measurments": 4,
"metrics": [
{
"date": "2015-02-17T06:04:15.251Z",
"duration": 696.713,
"emberVersion": "1.10.0",
"id": "8b6994ee-268b-43ce-8f26-96ddfcf466a7",
"pathname": "/metrics"
},
{
"date": "2015-02-17T06:04:06.243Z",
"duration": 903.421,
"emberVersion": "1.10.0",
"id": "28bad254-c3b9-462e-9966-4bc2fcc8fc63",
"pathname": "/metrics"
},
{
"date": "2015-02-17T06:02:24.487Z",
"duration": 660.65,
"emberVersion": "1.10.0",
"id": "11dfee70-d635-41fd-8e69-2df4f1c70dc1",
"pathname": "/metrics"
},
{
"date": "2015-02-17T06:02:24.021Z",
"duration": 734.586,
"emberVersion": "1.10.0",
"id": "b58722d9-c9eb-4ab2-bac7-41ba187a3646",
"pathname": "/metrics"
}
],
"name": "metrics_table",
"slow": 903.421,
"userAgents": [
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:33.0) Gecko/20100101 Firefox/33.0",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:37.0) Gecko/20100101 Firefox/37.0"
],
"emberVersion": "1.10",
"platform": "Macintosh",
"browser": "Firefox"
}
{
"metric": {
"durations": [
746.559,
1002.809
],
"fast": 746.559,
"geometric_mean": 865.249,
"measurments": 2,
"metrics": [
{
"date": "2015-02-17T03:02:27.682Z",
"duration": 746.559,
"emberVersion": "1.8.1",
"id": "d828e5b6-c5c2-4fee-8bf3-2d08d97ed36d",
"pathname": "/metrics"
},
{
"date": "2015-02-17T03:02:11.844Z",
"duration": 1002.809,
"emberVersion": "1.8.1",
"id": "e65effb9-4756-4642-8397-1757c4f3be15",
"pathname": "/metrics"
}
],
"name": "metrics_table",
"slow": 1002.809,
"userAgents": [
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:33.0) Gecko/20100101 Firefox/33.0",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:37.0) Gecko/20100101 Firefox/37.0"
],
"emberVersion": "1.8",
"platform": "Macintosh",
"browser": "Firefox"
}
}
</code></pre>
<p>Perhaps I'll use the queries and API endpoints for my metrics collection and reporting to see how the next version of Ember.js does, or see how a feature change impacts performance.</p>
EmberConf 2015 - March 4th (day 2)https://pixelhandler.dev/posts/emberconf-2015-day-22015-03-04T00:00:00+00:002015-03-04T00:00:00+00:00Billy Heaton@chriseppstein, maintenance of @Compass; works at LinkedIn<h2>Closing Keynote: Chris Eppstein, 5:30 — 6:00 PM</h2>
<ul>
<li>@chriseppstein, maintenance of @Compass; works at LinkedIn</li>
</ul>
<h4>Storytime, Once upon a time…</h4>
<ul>
<li>Rails Conf in Portland years ago</li>
<li>Sass, had a few patches</li>
<li>Have you heard of SproutCore? It's gonna be hard, frameworks are hard to maintain</li>
</ul>
<h4>Ruby Sass in the JS World</h4>
<ul>
<li>Hasn't been fun</li>
<li>Announcing: "Eyeglass", distribute SASS extension as NPM modules for LibSass (C++ implementation of SASS compiler)</li>
<li><a href="proxy.php?url=http://libsass.org" target="_blank" rel="noopener noreferrer">LibSass</a> is much faster than Ruby Sass</li>
<li>Compass is kind of "end of life" at this point, but it's not disappearing right away</li>
<li><a href="proxy.php?url=https://github.com/sass-eyeglass/eyeglass" target="_blank" rel="noopener noreferrer">Eyeglass</a>, developer preview, is not a bunch of mixins, e.g. autoprefixer; get the extensions in an let you use them in the JS world</li>
</ul>
<h4>Community is You</h4>
<ul>
<li>It's what makes software great</li>
<li>Be excellent to each other, how hard is it just to be nice?</li>
<li>Actually it is hard, "Turn the other cheek." - Jesus H. Christ</li>
<li>"Don't be a Sasshole." - Chris Eppstein</li>
<li>JavaScript community: You're kinda of Jerks to each other</li>
<li>"You are not your OSS contributions" - Don't take it personal</li>
<li>Know when it's time to step away from the keyboard</li>
</ul>
<h4>Conundrum</h4>
<ul>
<li>How to compete without being negative</li>
<li>Attack their weaknesses but ignore their strengths - bad idea.</li>
<li>Respect your competitors</li>
<li>Learn how to disagree and be disagreeable the secret of getting along</li>
</ul>
<h4>Confidence</h4>
<p>Is knowing you don't need to engage.</p>
<h4>Why is Ember going to Win?</h4>
<ul>
<li>Leadership, Us, backwords compatibility, tooling</li>
</ul>
<h4>What does "Win" Even win?</h4>
<ul>
<li>Find a job you love and you'll never work a day in your life</li>
<li>If you use a framework you love you'll never work a day in your live</li>
<li>Congrats you may have already one</li>
<li>If winning means being #1; the default choice, support is way harder, community? good luck</li>
<li>Being #2 is the sweet spot, people pick you because your are a good fit</li>
<li>The community you create is more awesome if your people fit</li>
<li>Focus on your strengths</li>
<li>LESS kind of collapsed under its own community</li>
<li>Don't stop learning from others but don't view it as a competition</li>
<li>Do what you do because you love it.</li>
</ul>
<h2>Physical Design: Edward Faulkner, 4:45 — 5:15 PM</h2>
<p>A talk about UX for the end users of our app, and for us developers.</p>
<ul>
<li>Abstract - (Almost) infinitely Flexible</li>
<li>Physical - Constrained by Physics</li>
</ul>
<p>Bring in constraints to our abstract applications the represent our physical world.</p>
<ul>
<li>Material design, spec - check it out, kick the tires on how it works
<ul>
<li>"Without breaking the rules of physics."</li>
<li>"Motion respects and reinforces the user as the prime mover"</li>
</ul></li>
</ul>
<h4>How does this fall into Ember's broader Architecture?</h4>
<p>Animations and motion in general, in reaction to the user's input. They live in the gaps in between what was and what becomes.</p>
<h4>Liquid-fire</h4>
<p><a href="proxy.php?url=https://github.com/ef4/liquid-fire" target="_blank" rel="noopener noreferrer">https://github.com/ef4/liquid-fire</a></p>
<ul>
<li>Version 1.0 - Coming soon</li>
<li>Showing off some animations in the EmberConf application</li>
<li>Liquid Outlet notices what is inside and manages changes / transitions</li>
<li>A Transitions module defines your animations similar to how the router defines routes</li>
<li>Goal Create extensible animations to share animations as Ember Addons</li>
<li>Liquid Fire organizes how you setup and when you call your animation library, firing off transitions e.g. before, after with the template content already positioned absolutely</li>
<li>Velocity.js is one recommended animation library that Liquid Fire can use to define modular animation and transitions</li>
<li>The animations are just functions you can receive arguments. </li>
<li>Each transition has an old and a new element.</li>
<li>Animation have a forward and reverse path to transition to your before and after states</li>
<li>The <code>explode</code> animation is pretty sweet, and the fly to is great</li>
<li>Tried out re-implementing the fly-to api with Web Animations API, instead of Velocity</li>
<li><code>liquid-with</code> & <code>liquid-each</code> - helpers for adding before and after elements when your model/properties change</li>
<li>An animation (function) can have many constraints and is triggered when all constraints match</li>
</ul>
<p>Perhaps checkout: <a href="proxy.php?url=http://miguelcobain.github.io/ember-paper/" target="_blank" rel="noopener noreferrer">http://miguelcobain.github.io/ember-paper/</a></p>
<h2>Minitalks: Leah Silber, 4:00 — 4:30 PM</h2>
<p>Sorry you had to be there :) nice variety of short talks on experimenting with Ember</p>
<h2>Building Real-time Applications With Ember: Steve Kinney, 3:00 — 3:30 PM</h2>
<p>@stevekinney | <a href="proxy.php?url=mailto:[email protected]" target="_blank" rel="noopener noreferrer">[email protected]</a></p>
<h4>How I chose Ember:</h4>
<ul>
<li>Lazy Twitter: Ember.js or Angular</li>
<li>wykatz - Ember.js, Although I'm biased ;)</li>
</ul>
<p>A Story about Web Sockets, about user sessions, GeoLocation, 3rd party Libraries</p>
<ul>
<li>What is a Web Socket? RFC 6455, Duh.</li>
</ul>
<p>Tradition: Request and Response cycle, but the server has to wait for a request, some clients may poll the server.</p>
<ul>
<li>A Web Socket is a two-way Pipe</li>
<li>Can I actually Use WebSockets? YEAH, except the Opera Mini browser.</li>
<li>Lots of libraries to give you fallbacks to long polling, e.g. Socket.io, Faye</li>
</ul>
<h4>Demo of building a chat app</h4>
<ul>
<li>We need a server, Ok - A Web Socket server in 14 lines of code with Node.js</li>
<li>Ember allows you to build your application and the DOM becomes a representation of your application</li>
<li><code>ember generate controller websocket</code> to create a controller, setup the socket on <code>init</code>
<ul>
<li>Create (un)subscribe handler and a send method</li>
</ul></li>
<li>Use <code>needs</code> to utilize the websocket controller</li>
<li>Add an action to respond to user's submitting a message to the chatroom</li>
</ul>
<h4>You share the struggles of making a better app with the community</h4>
<p>How to you bring the web socket behaviors and support into a service? search the forums, Oh there's Services, nice.</p>
<ul>
<li><code>ember g service websockets</code> then refactor the controller into a service</li>
<li>Instead of extending controller, extend Ember.Object</li>
<li><code>websocket: Ember.inject.service()</code></li>
<li>For Lulz bring in Socket.io with libraries on the client and server.</li>
<li>Server app can bind to events <code>socket.on('message')</code></li>
<li>In the client app subscribe to the messages and broadcast messags</li>
</ul>
<h4>Let's play</h4>
<p><a href="proxy.php?url=http://bit.ly/jspoll" target="_blank" rel="noopener noreferrer">http://bit.ly/jspoll</a> | code at: <a href="proxy.php?url=https://github.com/stevekinney/emberconf-chat" target="_blank" rel="noopener noreferrer">stevekinney/emberconf-chat</a></p>
<h2>Building Custom Apps with Ember Cli: Brittany Storoz, 2:15 — 2:45 Pm</h2>
<p>@brittanystoroz</p>
<h4>What were our needs? (Firefox OS Requirements)</h4>
<ul>
<li>Generage and validate a manifest file</li>
<li>UI components</li>
<li>publish to the marketplace</li>
</ul>
<p>The above use cases make for a good use case for using EMBER CLI Addon</p>
<ul>
<li>Demo: an EMBER CLI application, selecting timezones and local times</li>
</ul>
<p>Walkthrough creating an Addon to make the app a Firefox OC app</p>
<h4>Generate the Manifest (Creating blueprints for this purpose)</h4>
<ul>
<li> <code>ember generate blueprint name-of-module</code></li>
<li>Generates files an folders, named the blueprint after the name of her app</li>
<li>In the blueprint's directory mimic the file structure of the app, including the manifest</li>
<li>Put some files into the <code>public</code> directory</li>
<li>The blueprint affords using variables to generate application code that ships as your addon</li>
<li>There are hooks to utilize, e.g. postProcessTree, runs at the end of the build process, right before the app is written to the <code>dist</code> directory</li>
<li>You can use that hook to mutate the variables in the blueprint from a config file, e.g. update the name and the version of the application in its manifest file</li>
<li>Not only make the bower package available but also use the package, using the <code>included</code> hook to call <code>app.import()</code></li>
</ul>
<h4>UI Components</h4>
<ul>
<li>FxOS UI (Gaia): Building Components, a work in progress</li>
<li><code>bower install gaia-components/gaia-tabs</code></li>
<li>Setup the components (bower dependencies) and make them available to the consuming application, which needs to know it also requires the dependencies</li>
<li>Use the blueprint's <code>afterInstall</code> hook to add the bower package to the app</li>
<li>In the addon create an Ember.Component (with setup and teardown logic) then tell the consuming app to use that component, so in the <code>app</code> directory import the addon/component-name and export, now the app can use the component, it's registered in the app's container now.</li>
<li>Also add a template for the Component, in the demo a template was needed for the clock and timezone elements in the app</li>
</ul>
<h4>Validation and publishing</h4>
<ul>
<li>Adding commands, need finer controller for when things happen (jobs)</li>
<li>In the <code>lib/commands</code> directory create the command (name, alias, <code>run</code> method)</li>
<li>A command to validate the manifest file, create a command and let the app know that the command can be used</li>
<li>hook <code>includedCommands</code> require and return the imported lib/commands</li>
<li><code>ember fxos:validate</code> & <code>ember fxos:publish</code> does the custom work needed!</li>
<li>A property of commands is <code>availableOptions</code> as options hash in the method, e.g. pass in a dist target, the production marketplace, sweet
<ul>
<li>App created, app ID, you can see it in the marketplace</li>
</ul></li>
</ul>
<h2>Interaction Design With Ember 2.0 And Polymer: Bryan Langslet, 1:30 — 2:00 PM</h2>
<p>Blur the lines between native and Web to create an immersive experience.</p>
<p>@blangslet shared a reel of integrations between Ember and Polymer, continuous flow in an app, giving users context clarity and focus</p>
<p>Also showing off great demos of using Ember and Polymer together. "Demo with Pirate Music" is awesome!</p>
<ul>
<li>Demo App on github: <a href="proxy.php?url=https://github.com/blangslet/treasure-hunt" target="_blank" rel="noopener noreferrer">blangslet/treasure-hunt</a></li>
<li>Champions Web Platform nicely running inside native app built with Cordova iOS</li>
</ul>
<h4>Dream Stack</h4>
<ul>
<li>Ember for routing</li>
<li>Polymer layout grid, flexbox</li>
<li>Ember Components + Polymer</li>
<li>Polyfills</li>
</ul>
<h5>Ember going forward</h5>
<ul>
<li>Use tomorrows tech today</li>
</ul>
<h4>Web Components</h4>
<ul>
<li>Extend the browser itself</li>
<li>Encapsultation</li>
<li>Declarative</li>
<li>True reusability and portability</li>
</ul>
<h5>Ember vs Polymer Use Cases</h5>
<ul>
<li>Developer productivity</li>
<li>Conventions and developer happiness</li>
<li>Brilliant community</li>
<li>World class router</li>
<li>Polymer pushing the spec forward</li>
</ul>
<h5>Web Animations API</h5>
<ul>
<li>Best of both CSS and JavaScript animations</li>
<li>Run outside the main thread</li>
</ul>
<h4>The Web Platform</h4>
<ul>
<li>Turmoil in the Web landscape</li>
<li>Never underestimate the underdog, "Cool Runnings"</li>
<li>I'm a huge believer of the Web, everything has to be connected to the Web</li>
<li>Web Performance is on the rise, getting closer to native performance, still a ways to go, but rendering engines are getting faster</li>
<li>A lot of heavy lifting is moving to the client (JavaScript runtime)</li>
<li>Yes Native apps are fast, but what about the best tools available for the job, Web tools are getting awesome!</li>
</ul>
<p>A Path forward, stand on the shoulders of giants, Ember, Polymer, Web Components, etc.</p>
<h4>Ember-Flow</h4>
<p>A shift in adding animations and transitions to Ember Apps</p>
<p></p><hr><p></p>
<h2>Growing Ember One Tomster at a Time: Jamie White, 11:30 - 12:00 PM</h2>
<ul>
<li>Ember London is growing 600 members on meetup.com, 2 events a month</li>
</ul>
<p>The Tomster wasn't overly done, there are still choices to be make and things you can contribute to.</p>
<p>The code is a snapshot of the best ideas in our community, we're trying to create the best possible home for our idea</p>
<ul>
<li>Community design and API design are the same.</li>
</ul>
<h4>Roles in the Community</h4>
<ul>
<li>organizer, documenteer, contributor, mentor, student, explorer, critic, etc.</li>
<li>loads of roles inside and outside the community</li>
</ul>
<h4>Community Interface</h4>
<p>Is a README enough, How about a website</p>
<p>A consistent visual language build trust in your community, setting expectations across projects</p>
<p>Once an established visual language is set it becomes a tool for contribution, e.g. the redesign of Ember community pages (easier to find a meetup location)</p>
<h4>Analogy</h4>
<p>Consider promises, thing about other things while you know that asynchronicity is taken care of</p>
<h4>Language - words stick</h4>
<p>"Stability with out Stagnation" - terse and describes a larger idea</p>
<p>How about reframing the term 'hack night' to 'bring your own project' its a vocabulary that is more open to women and men.</p>
<h4>How did a tech community become this vibrant?</h4>
<p>design and engineering (the community that is not the software)</p>
<h4>The Tomster, an illustration to characterize the project, developer friendliness and productivity</h4>
<p>The Ember release cycle is inspired but the Chrome build release cycle but has no characterized illustation, (Is this for me?)</p>
<p>You can affordable commission and license a Tomster for your community!</p>
<p>Doing the right think should be easier than doing the wrong thing</p>
<ul>
<li>Ambition is not normally aside the concept of Friendliness, yet you can put a Tomster face on Ambition</li>
</ul>
<h2>Aligning Ember with Web Standards: Matthew Beale: 10:45 - 11:15 AM</h2>
<h4>Ember community survey</h4>
<ul>
<li><a href="proxy.php?url=http://www.201-created.com/ember-community-survey-2015" target="_blank" rel="noopener noreferrer">201-created.com/ember-community-survey-2015</a></li>
<li>75% of user are using the latest version of Ember.js</li>
</ul>
<p>Standards are a two-way street</p>
<h4>Strategy for adopting new features of JavaScript</h4>
<ol>
<li>provide legacy wrapper</li>
<li>use syntax as a carrot</li>
<li>private use can start sooner</li>
</ol>
<p>It's not a straight to adopt pure ES Classes as some concepts still need to be supported, e.g. calling this._super</p>
<h4>For example: Decorators</h4>
<pre class="highlight"><code>class Car {
attr('attachThings')
}
</code></pre>
<h4>Ember's Object Model</h4>
<p>Process of change to Ember's primitive for Objects</p>
<ol>
<li>stable</li>
<li>a good pattern</li>
<li>implemented correctly</li>
<li>implemented performantly</li>
</ol>
<p>Three new tools…</p>
<h4>Classes</h4>
<p>Gotchas:</p>
<ol>
<li>new syntax</li>
<li>super semantics change</li>
<li>mixins</li>
<li>setUnknownProperty</li>
<li>merged /concat</li>
<li>transpiler output?!</li>
</ol>
<h4>This is not the first time we had to move across standards</h4>
<p>"Polyfill" - Remy Sharp (inspired by spackle to fill the nail holes in your wall)</p>
<ul>
<li>Some things can be pollyfill'd and some new spec cannot</li>
<li>Transpile that new ES2015 code with <a href="proxy.php?url=https://babeljs.io" target="_blank" rel="noopener noreferrer">Babel</a> (not born to die)</li>
</ul>
<h4>Standards…</h4>
<ol>
<li>are portable</li>
<li>reflect best practices</li>
<li>endure</li>
</ol>
<h4>Participants win!</h4>
<ul>
<li>Promises and ES6 modules are things the Ember picked up early</li>
</ul>
<h4>Standards Story</h4>
<ul>
<li>WHATWG + W3C</li>
<li>TC39 + Ecma International</li>
<li>In 2008 WHATWG given the task of creating HTML5, realized HTML would never be done
<ul>
<li>Moved to a new model… A Living Standard, increments over time</li>
</ul></li>
<li>ES3 (used in IE8), then there was an attempt to create ES4 but that failed
<ul>
<li>So a few years later and + Web 2.0 they came up with ES5</li>
</ul></li>
<li>Then A new giant release for ES6, instead now we have ES2015, moved from a giant hover boat approach to a sailboat, sailing into 2015, then 2016, etc.</li>
</ul>
<p>Process for JavaScript releases</p>
<ol>
<li>Strawman</li>
<li>Proposal (polyfills)</li>
<li>Draft (experimental),</li>
<li>Candidate (Compliant)</li>
<li>Finished (Shipping)</li>
</ol>
<p>Javascript will come out every year! (try it out early Babel as es6ro5)</p>
<h2>Fault Tolerant UX: Dan Gebhardt, 10:00 - 10:30 AM</h2>
<p>Links</p>
<ul>
<li>Twitter: @orbitjs</li>
<li>IRC: #orbitjs</li>
<li>github/orbitjs</li>
<li>Soon <a href="proxy.php?url=http://orbitjs.com" target="_blank" rel="noopener noreferrer">http://orbitjs.com</a></li>
</ul>
<h4>Ember Orbit</h4>
<ul>
<li>EO.Store - Synchronous and Asynchronous interfaces</li>
<li>EO.Model - informs Orbit of the Schema</li>
<li>Orbit Sources DRIVE model state</li>
<li>Client-first development</li>
<li>Pluggable Sources</li>
<li>Data synchronization</li>
<li>Editing Isolation, Editing Context</li>
<li>Undo / Redo</li>
</ul>
<h4>Orbit.JS and JSON Patch</h4>
<ul>
<li>Transformable and Requestable interfaces</li>
<li>Synchronous Event Handling - Promise aware (async blocking, sources opt-in)</li>
<li>Non-Promise aware (non-blocking, sources opt-out of promise)</li>
<li>Connect events between Sources</li>
<li>Memory, Local Storage, JSON API, + More +</li>
<li>Orbit will be JSON API compliant, v 1.0 of JSON API tomorrow</li>
</ul>
<h4>Engineering Fault Tolerance</h4>
<ul>
<li>Ember provides Simple Elegant Patterns for building a Consistent UX</li>
<li>Ember Data: Consistent + Durability</li>
<li>ACID is hard: Atomic consistent Isolated Durable</li>
<li>Rethink Assumptions and Primitives</li>
<li>Disparate Sources</li>
<li>Disparate Data</li>
<li>Common Interfaces</li>
<li>Normalized Data</li>
<li>Evented Connections</li>
</ul>
<h4>Applications should try to Provide a forgiving UX, Please :)</h4>
<ul>
<li>Persist transient data, while still being edited, e.g. editing a comment in a code review</li>
<li>Undo and Redo, oops didn't mean to do that, e.g. Gmail can undo</li>
<li>Provide offline support, applications which are editing data in isolation, no immediate response from server is required, so your user should not be blocked from editing.</li>
<li>Asynchronous non-blocking interface, not blocked by request/response cycles from the server.</li>
</ul>
<h4>Applications MUST NOT Violate the Rules of Transactional UX, OR ELSE</h4>
<ul>
<li>Don't break your users' trust</li>
</ul>
<h4>Shield our users from environmental stresses that our application encounters</h4>
<ul>
<li>A transactional user experience, like a database has to reliable in committing data: ATOMIC</li>
<li>Atomic means "all or nothing"</li>
<li>A transaction should be consistent, it should move across all states - partials should not be come inconsistent</li>
<li>Transaction should be isolated and allow concurrent changes</li>
<li>Submit and cancel should just work, the change is isolated from the rest of the application, the change is done in isolation, separate from the canonical represtation</li>
</ul>
<h4>Fast forward to today</h4>
<ul>
<li>We're building complex systems and launching them into users browsers</li>
<li>Our Ember Apps allow users to navigate quickly and efficiently</li>
<li>Fault Tolerance in our environment can become stressfull - Browser wackiness</li>
<li>Plus some very optimistic users</li>
</ul>
<h4>Day tells the story around the beginning of his career working on ships.</h4>
<ul>
<li>Regulatory organizations must certify a ship, its controls and operational stability, etc.</li>
<li>Left that work for working on the internet in the 90s</li>
<li>Serious engineering and fault tolerance was expected on the server side</li>
<li>But the client side was much less serious.</li>
</ul>
EmberConf 2015 - March 3rd (day 1)https://pixelhandler.dev/posts/emberconf-2015-day-12015-03-03T00:00:00+00:002015-03-03T00:00:00+00:00Billy HeatonMaybe there are other people at the conference who build Web apps like I do, this is for you - Pro Tips about TDD…<h2>Test-Driven Development by Example: Toran Billups, 5:30 - 6:00 PM</h2>
<p>Maybe there are other people at the conference who build Web apps like I do, this is for you - Pro Tips about TDD…</p>
<p>Your tests should still pass between design iterations!</p>
<h4>Let's build a Kanban board, TDD style</h4>
<p>Live coding building a mini app from scratch using Ember CLI, and using test-driven development.</p>
<ul>
<li>Using an ember-cli app, start by generating an acceptance test, assignment</li>
<li><code>visiting /assignment</code> use <code>ember test --server</code> start with failing test 3 unassigned items</li>
<li>Define a route 'todos'</li>
<li>Red -> Green -> Refactor; it's OK to keep using Red for immediate feedback</li>
<li>Add a "todos" controller with the ember generator</li>
<li>Create the <code>unassigned</code> property, return an array with 3 items in it</li>
<li>Test result is green</li>
<li>In the acceptance test add a specific assertion for a seletector you'll use in your template</li>
<li>Add some sample data in the controller, 3 objects each with a projects property</li>
<li>Oops the tests were dependent on the HTML markup, instead remove any specific assertions about the layout (elements), use classes instead</li>
<li>Add the <code>assigned</code> property to the controller to iterate on the acceptance tests</li>
<li>Fixup the template to include the property that was in the test/assertion</li>
<li>Add a new property to the model object for the status</li>
<li>Rip out the mock model objects from the controller and paste into the Route's model hook</li>
<li>Add the status property to the template</li>
<li>Acceptance testing is great to show the happy path flow from your route to controller</li>
<li>Time to break out of acceptance testing and into unit testing so explore the transition between a status of 'unassigned' to 'assigned'</li>
<li>Unit test run in isolation, you can run just one test, <code>ember test --filter='todo'</code></li>
<li>Create a Todo model and a computed property for <code>status</code>, match up the lingo your template to a boolean property of the status_code</li>
<li>Every piece of test code is code you maintain, it is ok to delete a test, if another test already tests the same behavior</li>
<li>Write another acceptance test for toggling the status from unassigned to assigned</li>
<li>Test code documents your app, so it's a good idea to tell the before and after story
<ul>
<li>Create some assertions for the state before (in the arrangement) you act, then assert.</li>
</ul></li>
<li>Figure out how to mock the XHR and move out the hardcoded model from the route's model hook</li>
<li>In the route hook add the code to make the XHR request</li>
<li>Mock the XHR and in your test simulate the response</li>
<li>Add a new acceptance test to show the details for an item, <code>visit</code>, <code>click</code>, <code>andThen</code></li>
<li>Add a <code>link-to</code> helper in the template for the details, give it a class that your test can use for a selector</li>
<li>Add a nested route for /todo/:todo_id</li>
<li>Oops the test breaks, the data set needs an ID property</li>
<li>Assume that the details info is hidden, so update the test so assert that the content is hidden</li>
<li>When the URL is toggled then the details content hides or shows</li>
<li>Check the current route name to see what the URL is represented by (in your test)</li>
<li>Go back to the template and add an outlet</li>
</ul>
<h2>Dynamic Graphic Composition in Ember: Chris Henn, 4:45 - 5:15 PM</h2>
<h4>When is the Grammer of Graphics appropriate?</h4>
<ul>
<li>You decide how much of this abstraction you would like to apply</li>
<li>General purpose plotting library vs. App with one visualization</li>
</ul>
<h4>Considerations</h4>
<ul>
<li>Interactivity?</li>
<li>Animations and Transitions
<ul>
<li>The how point of the visualization is to convey data, use transitions (D3)</li>
</ul></li>
</ul>
<p>Breaking graphic down into parts allows you to add simple snippets of interactivity or transitions.</p>
<h4>What does this look like in Ember</h4>
<ul>
<li>Data to Aesthetic mappings</li>
<li>Scales: a constructor / function ( including its inverse)
<ul>
<li>in Ember can use a computed property (macro)</li>
</ul></li>
<li>Layers: each layer as a separate component
<ul>
<li>Generalizable, like playing with Legos</li>
</ul></li>
<li>Geometries: SVG</li>
</ul>
<h4>How to we split a statistical graphic into parts?</h4>
<ul>
<li>Faceting: How to compose data visualizations</li>
<li>A coordinate system, e.g. plots, circular plane</li>
<li>Layers: Geometry / Stat / Optional Data to Aesthetic mapping</li>
<li>Representing the mappings as points, the Geometry</li>
<li>For every data to aesthetic mapping there is a scale, a mapping function</li>
<li>A Simple scatter plot with 3 mappings (data to aesthetic mapping)</li>
<li>Grammer of Graphics, Hadley Wickham</li>
<li>Need: a flexible system</li>
<li>Adding a block param layer and adding multiple layers</li>
<li>Do heavier cars get worse mileage? (DEMO - scatterplot)
<ul>
<li>Graph: Horizontal:weight / Vertical:mileage</li>
</ul></li>
<li>Encourages custom visualizations</li>
<li>Suggests the aspects of a plot that are possible to change</li>
<li>Lets change one feature of the graphic at a time</li>
<li>Tackle it with composition</li>
</ul>
<p><em>Use Ember to take advantage of visualizations</em></p>
<p><a href="proxy.php?url=http://chrishenn.net" target="_blank" rel="noopener noreferrer">http://chrishenn.net</a> | Twitter: @cwhnn | github: chnn/composing-graphics</p>
<h2>Bring Sanity to Frontend Infrastructure with Ember: Sam Selikoff, 4:00 - 4:30 PM</h2>
<p>(latest updates flow in at the top)</p>
<p>Develop successful solutions for solid primitives is found in iteration and innovation</p>
<ol>
<li>Give developers the tools to tinker</li>
<li>Deliberately fold in shared solutions</li>
<li>Innovate and Share!</li>
</ol>
<p>Acme was right to start with a monolith, but over time they would identify the right abstractions and solutions for their framework and tooling. </p>
<h4>Ember CLI - future freshness baked in</h4>
<ul>
<li>The Ember community is relentless about finding and promoting best practices as they emerge as standards</li>
<li>Trust: new standards and best practices from the Web community will eventually land in the APIs found in Ember CLI</li>
<li>Overall architecture is more flexible so sub-sets of the system can be swapped out</li>
<li>semver + CLI conventions</li>
</ul>
<h4>Process</h4>
<p>Identify Redundancies and Abstractions</p>
<p>It's important to have a process around discovering the right abstractions</p>
<h4>Testing</h4>
<ul>
<li>ember generate factory</li>
<li>Addon for data concerns, e.g. http mocks; more consistency in the ecosystem</li>
<li>More similarities emerged, mocking data, proxies</li>
<li><code>ember test</code> provides a test harness that is common</li>
</ul>
<h4>Solutions Ember CLI brought for deployment concerns</h4>
<ol>
<li>manual</li>
<li>shared script</li>
<li>deploy addon</li>
<li>deploy server</li>
</ol>
<h4>Deployments</h4>
<ul>
<li>ember-deploy was all they needed to ship applications to production, various components were created as addons (pluggable)</li>
<li>Push an index.html and JS/CSS, backend can work with that consistency</li>
<li>Wrote a script for CI (continuous integration)</li>
<li>Conventions for building clarified how to distribute the application</li>
</ul>
<h4>Ember and Ember CLI</h4>
<ul>
<li>Ember brings in solid conventions for frontend application development</li>
<li>Command line tools: <code>ember</code> new, build, generate</li>
<li>Reduce boilerplate, eliminate trivial differences that hold us back</li>
</ul>
<h4>A story about the growth of a codebase, from monolith to various services, data layers and frontend interfaces (UI)</h4>
<ul>
<li>One day the frontend teams adopted Ember CLI and immediately had common solutions</li>
<li>Frontends interfaces each had resulting choices/adoptions with variations and incompatibilities</li>
<li>Every time they needed a new interface the programs had to make a lot of decisions about libraries, architecture, build pipeline, testing solutions and delivery systems</li>
<li>The Frontend didn't just split up and scale</li>
<li>Backend systems grew and spawned independent services, smaller and focused; developer were productive and happy</li>
</ul>
<h2>Ambitious UX for Ambitious Apps: Lauren Tan, 3:00 - 3:30 PM</h2>
<p>Good Design is Reactive, Playful, Informative and Intuitive…</p>
<h4>Design is not how it looks and feels its about how it works</h4>
<ul>
<li><a href="proxy.php?url=https://speakerdeck.com/poteto/emberconf-2015-ambitious-ux-for-ambitious-apps" target="_blank" rel="noopener noreferrer">Slides</a></li>
</ul>
<h4>Drag and Drop</h4>
<ul>
<li><a href="proxy.php?url=https://medium.com/delightful-ui-for-ember-apps/ember-js-and-html5-drag-and-drop-fa5dfe478a9a" target="_blank" rel="noopener noreferrer">Ember.js and HTML5 Drag and Drop</a></li>
</ul>
<h4>Design of a Messaging Service</h4>
<ul>
<li><a href="proxy.php?url=https://github.com/poteto/ember-cli-flash" target="_blank" rel="noopener noreferrer">ember-cli-flash</a></li>
<li><a href="proxy.php?url=https://medium.com/delightful-ui-for-ember-apps/adding-flash-messages-to-an-ember-app-437b13e49c1b" target="_blank" rel="noopener noreferrer">Ember Flash Message Demo</a> <code>ember-cli-flash</code></li>
<li>Can use a lifecycle for a message instance using <code>.destroy()</code></li>
<li>Ember.Object for the message model - can have methods for the supported macros</li>
<li>Messages can be wrapped in a component and queued in a service object for application notifications</li>
<li>Flash messages or growl messages</li>
<li>Visibility of system status, Jacob Nielsen</li>
</ul>
<h4>Using Services</h4>
<ul>
<li>demo of inserting a clever shower thought in to a loading message</li>
<li>Inject macro that is not cached and randomized</li>
</ul>
<h4>Think about good UX</h4>
<ul>
<li>If your shirt isn't tucked into your pants then your pants are tucked into your shirt</li>
<li>Reddit - shower thoughts</li>
<li>Clever loading messages</li>
</ul>
<h4>FRP in Ember</h4>
<ul>
<li>Observers invoked when dependent properties change</li>
<li>Ember.computed.X see the api docs</li>
<li>Computed properties and Observers, write macros too</li>
<li>Bacon.js - FRP, but Ember is different - the observer pattern</li>
<li>Functional Reactive Programming (FRP) - event streams and properties
<ul>
<li>A sequence (stream) of events and properties, consider the mouse movements, and it's properties for it's physical position on the screen</li>
</ul></li>
</ul>
<h4>What is Good Design</h4>
<ul>
<li>Ember enables reactivity via observers</li>
<li>Reative programming: continous flow of data and maintaining the relationships between the data</li>
<li>It's reactive</li>
</ul>
<h4>Design is how it works</h4>
<ul>
<li>It doesn't matter what we think as developers, we need to consider what our users think. Design is really important</li>
<li>Give clear answers to your users, don't mess with their experience</li>
<li>But I'm not a designer</li>
</ul>
<h4>About Lauren</h4>
<ul>
<li>Lauren - Designer and Front end developer at Homely ( in Austalia ) @sugarpirate</li>
</ul>
<h2>The Art of Ember Deployment: Luke Melia, 1:25 - 2:45 PM</h2>
<p>Awesome new story for deploying an Ember application: <a href="proxy.php?url=https://github.com/ember-cli/ember-cli-deploy" target="_blank" rel="noopener noreferrer">ember-cli/ember-cli-deploy</a></p>
<p>Real Artist Deploy</p>
<h4>We still need some collaberation</h4>
<ul>
<li>Let's talk… how will this work with FastBoot?</li>
<li>What is the relationship between the FastBoot server and your application server</li>
</ul>
<h4>Roadmap for ember-cli-deploy</h4>
<ul>
<li>After initial release
<ul>
<li>deploy to named buckets, deploy config instead of HTML, e.g. manifest from static build, let server drive what is sent to the client</li>
</ul></li>
<li>Usage: builds, deploys assets, can rollback, deploy targets, e.g. staging, give you a preview URL
<ul>
<li>Activate using an identifier after testing on live</li>
</ul></li>
<li>ember-cli-deploy plugins should use one or more of the hooks, willDeploy, build, updateAssets, updateIndex, activate, didDeploy, list</li>
<li>New pipeline, hooks and plugins architecture, similar to Ember Addons
<ul>
<li>e.g. Post deploy hooks</li>
</ul></li>
<li>Documentation for plugin developers</li>
<li>Release the end of this week</li>
<li>Update existing plugins</li>
</ul>
<h4>Pluggable deployment solutions</h4>
<ul>
<li>Paving the cow paths…</li>
<li>Merged the 3 projects, one awesome project <a href="proxy.php?url=https://github.com/ember-cli/ember-cli-deploy" target="_blank" rel="noopener noreferrer">ember-cli/ember-cli-deploy</a> with a team of six maintainers</li>
<li>ember-deploy has the biggest eco-system</li>
<li>projects: ember-deploy, ember-cli-deploy, front-end-builds, etc</li>
</ul>
<h4>New Possibilities when deploying a JS App independently from server apps</h4>
<ul>
<li>Notifying connected clients, hey user - it's time to refresh</li>
<li>How about serving various version of an app simultaneously</li>
<li>Dynamic HTML Rewriting, e.g. a Rails controller
<ul>
<li>CSRF tokens, dynamic analytics, A/B testing, feature flags</li>
</ul></li>
<li>Add a new level of confidence by going live once the app is tested on production</li>
<li>Preview the new version of the application</li>
</ul>
<h4>Modern Flow for deploying Web apps…</h4>
<ul>
<li>How about Redis, deploy the HTML (index page for a single page app) into Redis, Rails server reading out of Redis and assets deployed to S3</li>
<li>Do we deploy the built JS app to S3, but S3 reads can be slow, need speed</li>
<li>Deploying JS apps should be decoupled from server application deployments and restarts, basically UI deploys independently from backend</li>
<li>Caching should be minimal to none, so new users get the latest versions</li>
<li>HTML apps need fingerprinted assets for JS/CSS, specific config data for that current version of the app</li>
<li>For connected clients consuming a changing API they need the previous version running. Can we do the same for our Ember Apps?</li>
<li>During deployment need two apps running concurrently, how about versioning API and client app together? (its a fallacy)</li>
<li>Long-lived session and API changes
<ul>
<li>in the deployment cycle users may get an incorrect data representation</li>
</ul></li>
<li>Keep static assets working during rolling restarts
<ul>
<li>deploy old assets to a CDN and keep for awhile</li>
</ul></li>
</ul>
<h4>Looking back at how we deployed software</h4>
<ul>
<li>We no longer deploy individual files to the Web, Fetch app</li>
<li>We no longer use CD ROM duplicators to deploy web software</li>
<li>So how do we deliver software to the Web?</li>
<li>Then came the internet</li>
<li>Managing software over time… cassettes, cartridges, etc.</li>
<li>Luke organizes NYC Ember meetup and runs @YappLabs.</li>
</ul>
<h2>Hijacking Hacker News with Ember.js: Godfrey Chan, 1:30 - 2:00 PM</h2>
<p>if @chancancode so can you…</p>
<ul>
<li>New project (joke) CocoaBars, has 0 commits so get to work :)</li>
</ul>
<p>Chrome Extension: <a href="proxy.php?url=https://chrome.google.com/webstore/detail/hn-reader/emgghjnnkkopedbjfajejpkidaiedhlf" target="_blank" rel="noopener noreferrer">https://chrome.google.com/webstore/detail/hn-reader/emgghjnnkkopedbjfajejpkidaiedhlf</a></p>
<p>The power of the Adapter pattern in Ember allows you to customize your data mapping needs. You get small libraries to build your custom solutions on top of. Even if the frameworks opinions don't fit your needs the Adapter pattern opens up limitless possibility.</p>
<h4>Adapter Patterns</h4>
<ul>
<li>Used Ember.Observable get, set, addObserver to wrap localStorage API</li>
<li>use localStorage API to back data in the extension routes</li>
<li>Ember.History (Location API in the router) has <code>getURL</code> and <code>formatURL</code> hooks</li>
<li>Extension needed 100% URL compatibility with Hacker News website</li>
<li>Router API has flexibility on how you name your outlets</li>
<li>Ran into a problem with URL's as Ember Data is opinionated about how to construct URLs</li>
<li>Adapter is responsible for talking to server, and Serializer is responsible for transforming data</li>
<li>Drop in an Adapter to talk to your local store needs in an extension even though the adapter is not talking to an API</li>
<li>JSON API / Ember Data work together well, but what about a custom data source?</li>
<li>Today Ember Data is a local object store so can use an Adapter the way you choose</li>
<li>Now that we can get JSON data on the HN domain, can we store locally?</li>
<li>Extension is running on the HN domain so no need to cross-domain or API calls, just XHR /get</li>
<li>In the case of a chrome plugin app there is not necessarily a backend, fetching HTML and parsing from the Hacker news server in json</li>
</ul>
<h4>Demo of HN Reader App</h4>
<ul>
<li><a href="proxy.php?url=https://github.com/chancancode/hn-reader" target="_blank" rel="noopener noreferrer">hn-reader</a></li>
<li>Submitting extension to Hacker News in real-time</li>
<li>Chrome extension to hack the Hacker News website</li>
</ul>
<h4>Canadian Programmers</h4>
<p>Had some fun and laughs talking about Canadians talking to Americans…</p>
<ul>
<li><code>gem install canada</code></li>
<li><code>isEmpty.eh()?</code></li>
</ul>
<h2>Designing for Ember Apps: Steve Trevathan, 11:30 - 12:00 PM</h2>
<p>Coming soon… <em>Tools of the Trade</em> - a free design pattern library - <a href="proxy.php?url=http://toolsofthetrade.dockyard.com" target="_blank" rel="noopener noreferrer">http://toolsofthetrade.dockyard.com</a></p>
<h4>Create Repeatable Design Methods & Patterns</h4>
<ul>
<li>Offline Mode - "Goliath mode"
<ul>
<li>When a lost connection is restored then make doable updates (patches)</li>
<li>Responding Optimistically</li>
<li>Connection lost, but you can still use the app</li>
<li>Disallow editing? NO!</li>
</ul></li>
<li>Micro > Macro - Core interactions become a symbol of your app
<ul>
<li>iPhone thumbprint scanner, standard authorization step, repeated</li>
<li>The game "THREES" has one way to interact with the app</li>
</ul></li>
<li>Reuse Core interactions, e.g. card formats with common interfaces</li>
<li>Carry Context - don't leave me waiting at the bus stop
<ul>
<li>Can be a major save, imagine an online trade (investment) and your computer fails, so you pick up your phone and complete the transaction</li>
</ul></li>
<li>Skeleton UI - thinning out the spinners (e.g. gridlines where a map is loading tiles)
<ul>
<li>Draw an empty card format then fill in the details</li>
</ul></li>
<li>Gradual engagement - reducing the perceived risk</li>
</ul>
<h4>Mental Models: Use them explicitly and break them when you must</h4>
<ul>
<li>Remember the original iPod interface, a micro interaction on the wheel</li>
</ul>
<p><em>Digital Experiences</em></p>
<ul>
<li>Borrowing metal models is different that copying an application</li>
<li>Build a framework for understanding some applications have a difficult learning path</li>
<li>Take from existing models and meet expectations, both macro and micro, e.g. chat apps have been around for a while</li>
<li>The past: Razor phone, Instant Messanger app, chat app</li>
</ul>
<p><em>Micro Models</em></p>
<ul>
<li>How I think it works</li>
</ul>
<p><em>Mental Models:</em></p>
<ul>
<li>Can challenge or reinterpret incorrectly, be careful about pushing the limits of UX/design</li>
<li>What is "home" ? - Match peoples idea of what a home is and what you are building</li>
<li>Can be changed / updated</li>
<li>Influenced by experiences from my past</li>
</ul>
<p>How to we stop our users from spitting all over us? we don't marry them, but we do try to understand their mental models.</p>
<p>I'll be your "pain in the ass user" - I'm gonna hate the front door you build me</p>
<p>Metaphor <em>We're designing a house</em> - as we build an application with Ember. We're dealing with a large perhaps infinite number of use cases.</p>
<p>… loading presentation …</p>
<h2>Ember.js Performance: Stefan Penner, 10:45 - 11:15 AM</h2>
<h4>Mis-alignments to fix…</h4>
<ul>
<li>Combine approaches</li>
<li>Benchmarks can trick you</li>
<li>Premature optimization is not a good idea</li>
<li>Ember.Object.reopen - slowly augment objects; solution: limit reopen to only work until the constructor has it's first instance
<ul>
<li><code>actionsFor</code>, need to optimize the instances for meta - listeners (subscription)</li>
<li>Meta is 'live' inheriting - this is a problem</li>
<li>Meta, every class and every instance has a meta, it's the meta for the instances that kill performance</li>
</ul></li>
<li>init/super - need to embrace super
<ul>
<li>pass in args to init(args), this._super(args); use <code>arg.prop || 'default'</code>;</li>
</ul></li>
<li>Actions Up, bindings down</li>
<li>improved clarity makes the app faster</li>
</ul>
<h4>About the runtime and your code</h4>
<ul>
<li>Reasonable code should have reasonable cost</li>
<li>Allocation === cost, closures, unoptimized code allocates more objects</li>
<li>If the runtime believes the code is stable and predictable then it can optimize speed</li>
<li>Mad Science: Types + Stabiltiy</li>
<li>Need to understand the runtime, e.g. V8 or others
<ul>
<li>E.g. specialize type, specialized compiler methods - efficient native code</li>
</ul></li>
<li>"Mechanical Sympathy" - Jackie Stewart, align the car with the driver</li>
<li>"Only a sith uses abosolutes" - what are the performance questions that need to be answered?</li>
<li>Guided evolution
<ul>
<li>We have some choices to make.. productive for developers vs performance</li>
</ul></li>
<li>Why? we confuse ourselves and the runtime, we should understand the runtime</li>
<li>We still have work to do, the ceiling is still high</li>
<li>Bluebird, Promises made faster
<ul>
<li>Done without external API change or breakage</li>
<li>Did less work, allocate less objects, align with the underlining primitive</li>
<li>Promises using normal for loops is fast</li>
</ul></li>
</ul>
<p><strong>Myth? performance is about enumerating arrays</strong></p>
<h2>Opening Keynote: Yehuda Katz & Tom Dale, 9:45 - 10:30 AM</h2>
<p><strong>Your Antidote to Hype Fatigue</strong></p>
<h4>Introduction</h4>
<ul>
<li>625+ people in attendance and about 24 countries represented.</li>
<li>Ember is really growing this year, new apps built in Ember… Ghost, Beatport, Intercom.io, Customer.io, The Nest Store and more</li>
<li>The number of meetups has grown around the world</li>
<li>Special guest in the house an a Tomster mascott</li>
<li>Epic meetup video showing with a super long intro. <3</li>
<li>The core team is represented my many companies, 3 new people added today… Martin, Mixonic, & Edward</li>
</ul>
<h4>Report Card</h4>
<p>Last year we made lots of big promises, and guest what we shipped them.</p>
<ul>
<li>Early adopter of ES6 (ES2015) features… plan to be an early adopter of ES2016</li>
<li>Ember Data
<ul>
<li>Adapter Ecosystem</li>
<li>Relationships are always Async, build with async loading in mind</li>
<li>Data can come at any time and people really expect that data relationships are updated</li>
<li>On client get relationships on demand</li>
<li>Relationship Syncing, easy on the server not easy on the client (browser)</li>
<li>SSOT branch landed</li>
</ul></li>
<li>ES6 Modules, drive making the spec awesome but early adoption, helps out TC-39</li>
<li>Testing
<ul>
<li>At the end of 2014 and into 2015 has an awesome testing story</li>
<li>Out of the box with Ember CLI, functional and unit with great async support</li>
</ul></li>
<li>Ember CLI
<ul>
<li>Add whatever test harness you like</li>
<li>Scaffold new app instantly, and use various tooling to your liking, e.g. Coffeescript</li>
<li>Ember Addons - 663+ and growing, a single install command</li>
<li>testing, performance, stubbing, server proxy, content security policy, transpile with Babel, source maps,</li>
</ul></li>
<li>Ember Inspector
<ul>
<li>Thanks Teddy Zeenny</li>
<li>Firefox support, nav bar, bookmarklet</li>
<li>Performance pane</li>
<li>Promises pain has decreased</li>
<li>Ember Data pain has decreased</li>
</ul></li>
<li>RIP <code>script</code> tags as metamorphs</li>
<li>HTMLBars: At compile time your templates are validated and error feedback is reported; full compliant HTML parser</li>
<li><code><calendar-day></code> can remove <code>{{…}}</code> mustache syntax; TLDR - rebuild the render engine</li>
<li>Context of blocks, component scope… use block parameters today</li>
<li>HTMLBars <code><a href={{url}}></code></li>
<li>Rapid Release has worked really great, please run the beta release channel and report bugs
<ul>
<li>Robert Jackson <a href="proxy.php?url=http://getrwjblueabeer.com" target="_blank" rel="noopener noreferrer">http://getrwjblueabeer.com</a></li>
<li>RFC process, a couple dozen submitted</li>
</ul></li>
</ul>
<h2>Ember FastBoot</h2>
<ul>
<li>Bend the load time curve and front load the HTML</li>
<li>Can <code>curl</code> a Ember.js app, fastboot can be installed as middleware in an Express app</li>
<li>Ember is running as expected, but on the server</li>
<li>Lynx user now can user your Ember Apps, JavaScript disabled in the browser</li>
<li>Live demo of FastBoot, woot! "Musing of a Thoughtleader"</li>
<li>Boot up your app in a node process, hit the API server and render, just running on a different computer, not the browser</li>
<li>Run your app on the server and extract, abandon progressive enhancement</li>
</ul>
<h2>Performance</h2>
<ul>
<li>Ember release 1.13 will include <em>Glimmer</em> and will be compatible with your current apps</li>
<li>Is Ember Fast Yet? <a href="proxy.php?url=http://is-ember-fast-yet.firebaseapp.com" target="_blank" rel="noopener noreferrer">http://is-ember-fast-yet.firebaseapp.com</a></li>
<li>Glimmer, <em>the new rendering engine</em> built with HTMLBars</li>
<li>Confirm Ryan Floreance's complaint, stress test</li>
<li>Alternative Approach to Virtual DOM
<ul>
<li>Dirty checking for dynamic properties in the declarative syntax, we can know what parts will be changing</li>
<li>Only check for diff sets in the the changing pieces of the template</li>
<li>Handlebars, render the template, emit the DOM; while rendering track all the dynamic content (curlies <code>{{…}}</code>)</li>
</ul></li>
<li>Virtual DOM
<ul>
<li>Batch changes, since DOM is the bottleneck, smart refreshes</li>
<li>Manages rendering only the change sets, done in memory</li>
</ul></li>
<li>Render Virtual DOM and compare the DOM, diff then draw (ReactJS)</li>
<li>You can do a lot in JavaScript and less in DOM</li>
<li>Never say no to a feature, giving a reason of performance is not there</li>
</ul>
<h4>What's next in 2016</h4>
<ul>
<li>Ember FastBoot</li>
<li>Release Dates, beta candidates
<ul>
<li>ListView June 12th</li>
<li>Ember Data 1.0 is June 12th</li>
<li>Ember Inspector is June 12th</li>
<li>Ember 2.0 release is June 12th</li>
<li>concrete dates</li>
</ul></li>
<li>Ember Data
<ul>
<li>Ship It! we need a 1.0</li>
<li>Pagination & Filtering need first class support in JSON API transport</li>
<li>JSON API out of the box</li>
<li>JSON API designed from the ground up for what Ember Data needs</li>
<li>[json:api] spec, Steve K and Dan G. working hard on the spec</li>
</ul></li>
<li>Ember 2.0, the release plan is the Feature, rolling out in 1x release and under way
<ul>
<li>Keep up the rapid release pace, notify of deprecations often, remove on major releases</li>
</ul></li>
<li>Async & Routable Components</li>
<li>Liquid Fire @ef4 working closely so that HTMLBars is compatible</li>
<li><code><angle-bracket></code> Components, already in Canary</li>
<li>ListView (emberjs/list-view on github)</li>
<li>RFC: Engines, encapsulate application code for larger teams</li>
<li>Next Version: Ember CLI included in the guides</li>
<li>Versioned guides, live today, <a href="proxy.php?url=http://guides.emberjs.com" target="_blank" rel="noopener noreferrer">http://guides.emberjs.com</a></li>
</ul>
A Bet on Web Components and Ember.Component Synchronicityhttps://pixelhandler.dev/posts/a-bet-on-web-components-and-embercomponent-synchronicity2015-03-02T00:00:00+00:002015-03-02T00:00:00+00:00Billy HeatonThis article is a brief overview of both Web Components and Ember Components as well as a comparison of the two. You will see examples of blurring the lines between native Web Components and Ember ...<p>This article is a brief overview of both Web Components and Ember Components as well as a comparison of the two. You will see examples of blurring the lines between native Web Components and Ember components. Bits and pieces from the various specification(s), documentation sites and tutorials are highlighted (copied straightaway, follow links to see the full details). <em>The HTMLRocks tutorials were borrowed (copied) from heavily.</em><br>
With the Ember 2.X Evolution under way, you will have a new generation of first class citizens for your DOM, Ember.Components. Perhaps this overview will help you to design your Ember Components with future standards in mind, learn how to use native Web Components in synchronicity with Ember Components, and learn how to upgrade a native Web Component to an Ember.Component.</p>
<h2>An Ember.Component</h2>
<p>An <a href="proxy.php?url=http://emberjs.com/api/classes/Ember.Component.html" target="_blank" rel="noopener noreferrer">Ember.Component</a> is a view that is completely isolated.</p>
<ul>
<li>Property access in its templates go to the view object</li>
<li>Actions are targeted at the view object</li>
<li>No access to the surrounding context or outer controller</li>
<li>All contextual information must be passed into the component</li>
</ul>
<h3>Commonly Used Properties, Methods & Events…</h3>
<p><code>tagName</code>, <code>layoutName</code>, <code>willInsertElement</code>, <code>didInsertElement</code>, <code>actions</code>, <code>sendAction</code>, <code>targetObject</code>, <code>willDestroyElement</code>, <code>$</code>, <code>on</code>, <code>off</code></p>
<h2>A Web Component</h2>
<p>A Draft of Specifications: </p>
<ul>
<li><a href="proxy.php?url=http://www.w3.org/TR/html5/scripting-1.html#the-template-element" target="_blank" rel="noopener noreferrer">Template</a> (HTML5)</li>
<li><a href="proxy.php?url=http://www.w3.org/TR/html-imports/" target="_blank" rel="noopener noreferrer">HTMLImport</a> (Working Draft)</li>
<li><a href="proxy.php?url=http://w3c.github.io/webcomponents/spec/custom/" target="_blank" rel="noopener noreferrer">Custom Element</a> (Editor's Draft)</li>
<li><a href="proxy.php?url=http://w3c.github.io/webcomponents/spec/shadow/" target="_blank" rel="noopener noreferrer">Shadow DOM</a> (Editor's Draft)</li>
</ul>
<h3>Template</h3>
<p>The <a href="proxy.php?url=http://www.w3.org/TR/html5/scripting-1.html#the-template-element" target="_blank" rel="noopener noreferrer">Template</a> element is used to declare fragments of HTML that can be cloned and inserted in the document by script.</p>
<p>When web pages dynamically alter the contents of their documents, they may require fragments of HTML which may require further modification before use, e.g the insertion of values appropriate for the usage context.</p>
<p><code>template.content</code> Returns the template contents, which are stored in a DocumentFragment associated with a different Document (to avoid the template contents interfering with the main Document).</p>
<p>The template element doesn't provide data binding. <code><template></code> provides an ability to insert 'inert HTML tags' into a document, using inert HTML tags.</p>
<ul>
<li>Inlined scripts won't be executed without being stamped out</li>
<li>Resources such as <code><img></code> or <code><video></code> won't be fetched without being stamped out</li>
</ul>
<p>To define a template, simply wrap your content with a <code><template></code> tag.</p>
<p>In order to stamp out the <code><template></code>, you'll need to write a bit of JavaScript.</p>
<p><sup>Some content in the summaries above were taken directly from the <a href="proxy.php?url=http://www.w3.org/TR/#tr_Web_Components" target="_blank" rel="noopener noreferrer">W3C docs</a> and the <a href="proxy.php?url=http://www.html5rocks.com/en/search?q=Web+Components" target="_blank" rel="noopener noreferrer">HTMLRocks Tutorials</a></sup></p>
<h4>More info</h4>
<ul>
<li><a href="proxy.php?url=http://webcomponents.org/articles/introduction-to-template-element/" target="_blank" rel="noopener noreferrer">Introduction to Template Element</a></li>
<li><a href="proxy.php?url=http://www.w3.org/TR/html5/scripting-1.html#the-template-element" target="_blank" rel="noopener noreferrer">http://www.w3.org/TR/html5/scripting-1.html#the-template-element</a></li>
</ul>
<h3>HTMLImport</h3>
<p><a href="proxy.php?url=http://www.w3.org/TR/html-imports/" target="_blank" rel="noopener noreferrer">HTML Imports</a> are a way to include and reuse HTML documents in other HTML documents.</p>
<p>Imports are HTML docs, linked as external resources, from another HTML doc. The document that links to an import is called an import referrer. An import has an import referrer ancestor as its import referrer.</p>
<p>An import referrer which has its own browsing context is called a master document. Each import is associated with one master document.</p>
<p>The URL of an import is called the import location. In the import referrer, an import is represented as a Document, called the imported document. The imported documents don't have browsing context.</p>
<p>The set of all imports associated with the master document forms an import map of the master document. The maps stores imports as its items with their import locations as keys.</p>
<p><sup>Most of the content in the summaries above were copied directly from the <a href="proxy.php?url=http://www.w3.org/TR/#tr_Web_Components" target="_blank" rel="noopener noreferrer">W3C docs</a> and the <a href="proxy.php?url=http://www.html5rocks.com/en/search?q=Web+Components" target="_blank" rel="noopener noreferrer">HTMLRocks Tutorials</a></sup></p>
<h4><a href="proxy.php?url=https://hacks.mozilla.org/2014/12/mozilla-and-web-components/" target="_blank" rel="noopener noreferrer">Mozilla won't ship HTML Imports</a></h4>
<h3>Custom Element</h3>
<p><a href="proxy.php?url=http://w3c.github.io/webcomponents/spec/custom/" target="_blank" rel="noopener noreferrer">Custom Elements</a> provides a way for Web developers to build their own, fully-featured DOM elements.</p>
<p>It's possible to create DOM elements with any tag names in HTML, but these elements aren't very functional. Custom elements inform the parser how to properly construct an element & to react to lifecycle changes.</p>
<p>Rationalize the platform. The spec ensures that all of its new features & abilities are in concert with how the relevant bits of the Web platform work today</p>
<h4>Lifecycle: Types of Callbacks</h4>
<ul>
<li><code>createdCallback</code> - invoked after custom element instance is created and its definition is registered</li>
<li><code>attachedCallback</code> - whenever custom element is inserted into a document</li>
<li><code>detachedCallback</code> - whenever custom element is removed from the documen</li>
<li><code>attributeChangedCallback</code> - whenever custom element's attribute is added, changed or removed.</li>
</ul>
<p><sup>Most of the content in the summaries above were copied directly from the <a href="proxy.php?url=http://www.w3.org/TR/#tr_Web_Components" target="_blank" rel="noopener noreferrer">W3C docs</a> and the <a href="proxy.php?url=http://www.html5rocks.com/en/search?q=Web+Components" target="_blank" rel="noopener noreferrer">HTMLRocks Tutorials</a></sup></p>
<h3>Shadow DOM</h3>
<p>"A particularly pernicious aspect of the lack of encapsulation is that if you upgrade the library and the internal details of the widget’s DOM changes, your styles and scripts might break in unpredictable ways." -HTMLRocks tutorial</p>
<p><a href="proxy.php?url=http://w3c.github.io/webcomponents/spec/shadow/" target="_blank" rel="noopener noreferrer">Shadow DOM</a> addresses the DOM tree encapsulation problem.</p>
<p>Elements can get a new kind of node associated with them, a shadow root. An element with a shadow root is a shadow host. The content of a shadow host isn’t rendered; the content of the shadow root is rendered instead.</p>
<p>With Shadow DOM, all markup and CSS are scoped to the host element. CSS styles defined inside a Shadow Root won't affect its parent document, CSS styles defined outside the Shadow Root won't affect the main page.</p>
<p><sup>Most of the content in the summaries above were copied directly from the <a href="proxy.php?url=http://www.w3.org/TR/#tr_Web_Components" target="_blank" rel="noopener noreferrer">W3C docs</a> and the <a href="proxy.php?url=http://www.html5rocks.com/en/search?q=Web+Components" target="_blank" rel="noopener noreferrer">HTMLRocks Tutorials</a></sup></p>
<h4>Tutorials</h4>
<ul>
<li><a href="proxy.php?url=http://www.html5rocks.com/en/tutorials/webcomponents/shadowdom/" target="_blank" rel="noopener noreferrer">Shadow DOM 101</a></li>
<li><a href="proxy.php?url=http://www.html5rocks.com/en/tutorials/webcomponents/shadowdom-201/" target="_blank" rel="noopener noreferrer">Shadow DOM 201</a></li>
<li><a href="proxy.php?url=http://www.html5rocks.com/en/tutorials/webcomponents/shadowdom-301/" target="_blank" rel="noopener noreferrer">Shadow DOM 301</a></li>
</ul>
<h2>Compare & Contrast…</h2>
<p>How do the two components line up? Can they work together? What are the differences?</p>
<h3>Ember.Component</h3>
<ul>
<li>Scope is isolated, only knows the context passed in. Compared with a view that knows the scope of the current controller. Body of component (between the opening and closing tags) is in the same scope of the template it belongs too.</li>
<li>No encapsulation of styles, best to use a custom element and define a base style for the component.</li>
<li>You can use in an Ember App today, in any modern browser.</li>
<li>Specification - only works with Ember.js</li>
<li>Can use a 'layout' (template); compile with your build pipeline, e.g. ember-cli</li>
<li>Built-in data-binding (using Ember)</li>
<li>Lifecycle callbacks (Events): <code>didInsertElement</code>, <code>parentViewDidChange</code>, <code>willClearRender</code>, <code>willDestroyElement</code>, <code>willInsertElement</code></li>
<li>Create components that extend from other components</li>
<li>Bundle custom functionality into a single component</li>
<li>Use a custom tagName (custom element)</li>
<li>How do you distribute components, the Ember CLI 'addons' story is still in its infancy but becoming more attractive</li>
</ul>
<h3>Web Component: Custom Element</h3>
<ul>
<li><a href="proxy.php?url=http://caniuse.com/#search=Custom%20Element" target="_blank" rel="noopener noreferrer">caniuse Custom Element</a></li>
<li>Feature detecting is a matter of checking if <code>document.registerElement()</code> exists</li>
<li>The most important API primitive under the Web Components umbrella</li>
<li>Define new HTML/DOM elements</li>
<li>Create elements that extend from other elements</li>
<li>Logically bundle together custom functionality into a single tag</li>
<li>Extend the API of existing DOM elements</li>
</ul>
<pre class="highlight"><code><span class="kd">var</span> <span class="nx">XFoo</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nf">registerElement</span><span class="p">(</span><span class="dl">'</span><span class="s1">x-foo</span><span class="dl">'</span><span class="p">,</span> <span class="p">{</span>
<span class="na">prototype</span><span class="p">:</span> <span class="nb">Object</span><span class="p">.</span><span class="nf">create</span><span class="p">(</span><span class="nx">HTMLElement</span><span class="p">.</span><span class="nx">prototype</span><span class="p">,</span> <span class="p">{</span>
<span class="na">bar</span><span class="p">:</span> <span class="p">{</span>
<span class="na">get</span><span class="p">:</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span> <span class="k">return</span> <span class="mi">5</span><span class="p">;</span> <span class="p">}</span>
<span class="p">},</span>
<span class="na">foo</span><span class="p">:</span> <span class="p">{</span>
<span class="na">value</span><span class="p">:</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
<span class="nf">alert</span><span class="p">(</span><span class="dl">'</span><span class="s1">foo() called</span><span class="dl">'</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">})</span>
<span class="p">});</span>
</code></pre>
<p>Lifecycle callback methods: </p>
<ul>
<li><code>createdCallback</code> an instance of the element is created</li>
<li><code>attachedCallback</code> an instance was inserted into the document</li>
<li><code>detachedCallback</code> an instance was removed from the document</li>
<li><code>attributeChangedCallback(attrName, oldVal, newVal)</code> an attribute was added, removed, or updated</li>
</ul>
<h3>Web Component: Shadow DOM</h3>
<ul>
<li><a href="proxy.php?url=http://caniuse.com/#search=Shadow%20DOM" target="_blank" rel="noopener noreferrer">caniuse Shadow DOM</a></li>
<li>Shadow DOM is 'really' completely isolated, both in scope (context) and style.</li>
<li>The shadow root has it's own stylesheet.</li>
<li>There is a way with CSS to define styles outside of the component using <code>::shadow</code> pseudo-element.</li>
<li>By 'really' isolated we mean there is only a couple backdoors e.g. <code>::shadow</code> or <code>/deep/</code> using CSS or JavaScript (<code>querySelector</code>)</li>
<li>You can use only in Chrome</li>
<li>Specification is a work in progress</li>
</ul>
<p>For example, if an element is hosting a shadow root, you can write <code>#host::shadow span {}</code> to style all of the spans within its shadow tree.</p>
<p>(Code samples borrowed from HTMLRocks tutorials)</p>
<pre class="highlight"><code><span class="o"><</span><span class="nx">style</span><span class="o">></span>
<span class="nx">#host</span><span class="p">::</span><span class="nx">shadow</span> <span class="nx">span</span> <span class="p">{</span> <span class="nl">color</span><span class="p">:</span> <span class="nx">red</span><span class="p">;</span> <span class="p">}</span>
<span class="o"><</span><span class="sr">/style</span><span class="err">>
</span><span class="o"><</span><span class="nx">div</span> <span class="nx">id</span><span class="o">=</span><span class="dl">"</span><span class="s2">host</span><span class="dl">"</span><span class="o">><</span><span class="nx">span</span><span class="o">></span><span class="nx">Light</span> <span class="nx">DOM</span><span class="o"><</span><span class="sr">/span></</span><span class="nx">div</span><span class="o">></span>
<span class="o"><</span><span class="nx">script</span><span class="o">></span>
<span class="kd">var</span> <span class="nx">host</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nf">querySelector</span><span class="p">(</span><span class="dl">'</span><span class="s1">div</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">root</span> <span class="o">=</span> <span class="nx">host</span><span class="p">.</span><span class="nf">createShadowRoot</span><span class="p">();</span>
<span class="nx">root</span><span class="p">.</span><span class="nx">innerHTML</span> <span class="o">=</span> <span class="dl">'</span><span class="s1"><span>Shadow DOM</span></span><span class="dl">'</span> <span class="o">+</span>
<span class="dl">'</span><span class="s1"><content></content></span><span class="dl">'</span><span class="p">;</span>
<span class="o"><</span><span class="sr">/script</span><span class="err">>
</span></code></pre>
<p>Using JavaScript can the shadow tree be accessed? Yes, with the power comes the responsibility to respect encapsulation.</p>
<pre class="highlight"><code>document.querySelector('x-tabs::shadow x-panel::shadow #foo');
</code></pre>
<p>Do the <code>::shadow</code> pseudo-element and <code>/deep/</code> combinator defeat the purpose of style encapsulation? Shadow DOM prevents accidental styling from outsiders but never promises to be a bullet proof vest.</p>
<p>Developers should be allowed to intentionally style inner parts of your Shadow tree...if they know what they're doing. Having more control is also good for flexibility, theming, and the re-usability of your elements.</p>
<p><sup>Some of the content above was copied directly from the <a href="proxy.php?url=http://www.w3.org/TR/#tr_Web_Components" target="_blank" rel="noopener noreferrer">W3C docs</a> and the <a href="proxy.php?url=http://www.html5rocks.com/en/search?q=Web+Components" target="_blank" rel="noopener noreferrer">HTMLRocks Tutorials</a></sup></p>
<h3>Web Component: HTMLImport</h3>
<ul>
<li><a href="proxy.php?url=http://caniuse.com/#search=HTML%20Import" target="_blank" rel="noopener noreferrer">caniuse HTML Import</a></li>
<li>To detect support, check if .import exists on the element <code>'import' in document.createElement('link')</code></li>
<li>How many imports to you want to link to in the head of a document.</li>
<li>Perhaps you still need a build pipeline to minimize http requests</li>
<li>An import is just a document. The content of an import is called an <code>import</code> document <code>$('link[rel="import"]')[0].import;</code></li>
<li>Script in the import is executed in the context of the window that contains the importing document</li>
<li>Imports do not block parsing of the main page</li>
<li>The HTML Template element is a natural fit for HTML Imports</li>
<li>An ideal way to distribute Web Components</li>
</ul>
<h3>Web Component: Template Element</h3>
<ul>
<li><a href="proxy.php?url=http://caniuse.com/#search=Template" target="_blank" rel="noopener noreferrer">caniuse Template</a></li>
<li>Looks good to go in modern browsers (not Internet Explorer)</li>
<li>No built-in data-bindings, use Object.observe</li>
<li><a href="proxy.php?url=http://caniuse.com/#search=Object.observe" target="_blank" rel="noopener noreferrer">caniuse Object.observe</a></li>
<li>Feature detect <code><template></code>, create the DOM element and check that the .content property exists - <code>'content' in document.createElement('template')</code></li>
<li>Content is effectively inert until activated</li>
<li>Content within a template won't have side effects</li>
<li>Content is considered not to be in the document</li>
<li>The .content property is a read-only <code>DocumentFragment</code> containing the guts of the template</li>
<li>Create a deep copy of its .content using <code>document.importNode()</code></li>
</ul>
<pre class="highlight"><code><span class="kd">var</span> <span class="nx">t</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nf">querySelector</span><span class="p">(</span><span class="dl">'</span><span class="s1">#mytemplate</span><span class="dl">'</span><span class="p">);</span>
<span class="c1">// Populate the src at runtime.</span>
<span class="nx">t</span><span class="p">.</span><span class="nx">content</span><span class="p">.</span><span class="nf">querySelector</span><span class="p">(</span><span class="dl">'</span><span class="s1">img</span><span class="dl">'</span><span class="p">).</span><span class="nx">src</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">logo.png</span><span class="dl">'</span><span class="p">;</span>
<span class="kd">var</span> <span class="nx">clone</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nf">importNode</span><span class="p">(</span><span class="nx">t</span><span class="p">.</span><span class="nx">content</span><span class="p">,</span> <span class="kc">true</span><span class="p">);</span>
<span class="nb">document</span><span class="p">.</span><span class="nx">body</span><span class="p">.</span><span class="nf">appendChild</span><span class="p">(</span><span class="nx">clone</span><span class="p">);</span>
</code></pre>
<h2>Blurring the lines between Web Components and Ember.Component</h2>
<p>Below are a few code samples showing how to use the <code>tagName</code> of your Ember component that is based on a Custom Element (Web Component).</p>
<p>See the jsbin and github repos for demo links to inspect and see the code in action. Checkout the shadow root in your developer tools to see the shadow root of the Web components.</p>
<h3>Upgrade a native Custom Element into a Web Component</h3>
<ul>
<li>See <a href="proxy.php?url=http://jsbin.com/hafivocecu/1/edit?html,js,output" target="_blank" rel="noopener noreferrer">jayphelps jsbin</a> (code samples below copied from this jsbin)</li>
</ul>
<pre class="highlight"><code><span class="nt"><script </span><span class="na">type=</span><span class="s">"text/x-handlebars"</span><span class="nt">></span>
<span class="k">{{</span><span class="nv">input</span> <span class="nv">value</span><span class="o">=</span><span class="nv">value</span><span class="k">}}</span>
<span class="o"><</span><span class="nx">x</span><span class="o">-</span><span class="nx">ember</span> <span class="kd">class</span><span class="o">=</span><span class="dl">"</span><span class="s2">blue</span><span class="dl">"</span><span class="o">><</span><span class="sr">/x-ember</span><span class="err">>
</span> <span class="o"><</span><span class="nx">x</span><span class="o">-</span><span class="nx">native</span> <span class="nx">foo</span><span class="o">=</span><span class="k">{{</span><span class="nv">value</span><span class="k">}}</span> <span class="kd">class</span><span class="o">=</span><span class="dl">"</span><span class="s2">red</span><span class="dl">"</span><span class="o">></span>
<span class="k">{{</span><span class="nv">value</span><span class="k">}}</span>
<span class="o"><</span><span class="sr">/x-native</span><span class="err">>
</span><span class="nt"></script></span>
<span class="nt"><script </span><span class="na">type=</span><span class="s">"text/x-handlebars"</span> <span class="na">data-template-name=</span><span class="s">"components/x-ember"</span><span class="nt">></span>
<span class="nx">I</span> <span class="nx">am</span> <span class="nx">an</span> <span class="nx">ember</span> <span class="nx">component</span>
<span class="nt"></script></span>
</code></pre>
<p>Setup component generation...</p>
<pre class="highlight"><code>window.EmberENV = {
FEATURES: {
'ember-htmlbars-component-generation': true
}
};
</code></pre>
<p>Create a native Web Component...</p>
<pre class="highlight"><code><span class="c1">// Native Web Component</span>
<span class="kd">function</span> <span class="nf">XNativeElement</span><span class="p">()</span> <span class="p">{}</span>
<span class="nx">XNativeElement</span><span class="p">.</span><span class="nx">prototype</span> <span class="o">=</span> <span class="nb">Object</span><span class="p">.</span><span class="nf">create</span><span class="p">(</span><span class="nx">HTMLElement</span><span class="p">.</span><span class="nx">prototype</span><span class="p">);</span>
<span class="nb">document</span><span class="p">.</span><span class="nf">registerElement</span><span class="p">(</span><span class="dl">'</span><span class="s1">x-native</span><span class="dl">'</span><span class="p">,</span> <span class="nx">XNativeElement</span><span class="p">);</span>
<span class="c1">// Upgraded to an Ember Component using `x-native` element</span>
<span class="nx">App</span><span class="p">.</span><span class="nx">XNativeComponent</span> <span class="o">=</span> <span class="nx">Ember</span><span class="p">.</span><span class="nx">Component</span><span class="p">.</span><span class="nf">extend</span><span class="p">({</span>
<span class="na">tagName</span><span class="p">:</span> <span class="dl">'</span><span class="s1">x-native</span><span class="dl">'</span><span class="p">,</span>
<span class="na">attributeBindings</span><span class="p">:</span> <span class="p">[</span><span class="dl">'</span><span class="s1">foo</span><span class="dl">'</span><span class="p">]</span>
<span class="p">});</span>
</code></pre>
<h3>Upgrading a Web Component to an Ember.Component</h3>
<ul>
<li><a href="proxy.php?url=https://github.com/pixelhandler/alert-box-web-components" target="_blank" rel="noopener noreferrer">alert-box-web-components</a> - see examples/app.html</li>
</ul>
<p>I found that in Chrome, which supports Web Components, I can use a Custom Element as the <code>tagName</code> for an Ember Component. I reap the benefit from the behaviors provided by the native Web Component and the bindings provided by an Ember Component.</p>
<h4>Alert Box Web Components Demos</h4>
<p>These three demos show examples of using only Web Components, upgrading a Web Component to an Ember Component, and converting the Web Component to an Ember Component.</p>
<ul>
<li><a href="proxy.php?url=https://github.com/pixelhandler/alert-box-web-components#alert-box" target="_blank" rel="noopener noreferrer">pixelhandler/alert-box-web-components</a> - Repository for a native Web Component</li>
<li><a href="proxy.php?url=http://pixelhandler.github.io/alert-box-web-components/example/" target="_blank" rel="noopener noreferrer">Web Component only</a> - Example of the native Web Component in action</li>
<li><a href="proxy.php?url=http://pixelhandler.github.io/alert-box-web-components/example/app.html" target="_blank" rel="noopener noreferrer">Ember.js app using (native) Web Component</a> - Upgraded Web Component to an Ember Component</li>
<li><a href="proxy.php?url=http://pixelhandler.github.io/alert-box-web-components/example/ember-component.html" target="_blank" rel="noopener noreferrer">As an Ember.Component only</a> - Borrowed styles and behavior from the Web Component but only done in Ember</li>
</ul>
<h4>Alert Box Web Component Code:</h4>
<pre class="highlight"><code><span class="c"><!--</span>
<span class="nx">Web</span> <span class="nx">Component</span><span class="p">:</span> <span class="nx">Alert</span> <span class="nx">Box</span>
<span class="o">--></span>
<span class="o"><</span><span class="nx">template</span> <span class="nx">id</span><span class="o">=</span><span class="dl">"</span><span class="s2">alert-box</span><span class="dl">"</span><span class="o">></span>
<span class="o"><</span><span class="nx">style</span><span class="o">></span>
<span class="nx">html</span> <span class="p">{</span>
<span class="nx">box</span><span class="o">-</span><span class="nx">sizing</span><span class="p">:</span> <span class="nx">border</span><span class="o">-</span><span class="nx">box</span><span class="p">;</span>
<span class="p">}</span>
<span class="o">*</span><span class="p">,</span> <span class="o">*</span><span class="p">:</span><span class="nx">before</span><span class="p">,</span> <span class="o">*</span><span class="p">:</span><span class="nx">after</span> <span class="p">{</span>
<span class="nx">box</span><span class="o">-</span><span class="nx">sizing</span><span class="p">:</span> <span class="nx">inherit</span><span class="p">;</span>
<span class="p">}</span>
<span class="cm">/* alert-box */</span>
<span class="p">:</span><span class="nx">host</span> <span class="p">{</span>
<span class="nl">display</span><span class="p">:</span> <span class="nx">none</span><span class="p">;</span>
<span class="nl">width</span><span class="p">:</span> <span class="mi">400</span><span class="nx">px</span><span class="p">;</span>
<span class="nl">margin</span><span class="p">:</span> <span class="mi">15</span><span class="nx">px</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">:</span><span class="nf">host</span><span class="p">(.</span><span class="nx">ready</span><span class="p">)</span> <span class="p">{</span>
<span class="nl">display</span><span class="p">:</span> <span class="nx">block</span><span class="p">;</span>
<span class="p">}</span>
<span class="nx">section</span> <span class="p">{</span>
<span class="nx">background</span><span class="o">-</span><span class="nx">color</span><span class="p">:</span> <span class="err">#</span><span class="mi">337</span><span class="nx">ab7</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">:</span><span class="nf">host</span><span class="p">(.</span><span class="nx">red</span><span class="p">)</span> <span class="nx">section</span><span class="p">,</span>
<span class="p">:</span><span class="nf">host</span><span class="p">([</span><span class="nx">type</span><span class="o">=</span><span class="dl">"</span><span class="s2">danger</span><span class="dl">"</span><span class="p">])</span> <span class="nx">section</span><span class="p">,</span>
<span class="p">:</span><span class="nf">host</span><span class="p">([</span><span class="nx">type</span><span class="o">=</span><span class="dl">"</span><span class="s2">fail</span><span class="dl">"</span><span class="p">])</span> <span class="nx">section</span> <span class="p">{</span>
<span class="nx">background</span><span class="o">-</span><span class="nx">color</span><span class="p">:</span> <span class="nx">#D9534F</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">:</span><span class="nf">host</span><span class="p">(.</span><span class="nx">orange</span><span class="p">)</span> <span class="nx">section</span><span class="p">,</span>
<span class="p">:</span><span class="nf">host</span><span class="p">([</span><span class="nx">type</span><span class="o">=</span><span class="dl">"</span><span class="s2">warning</span><span class="dl">"</span><span class="p">])</span> <span class="nx">section</span> <span class="p">{</span>
<span class="nx">background</span><span class="o">-</span><span class="nx">color</span><span class="p">:</span> <span class="nx">#F0AD4E</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">:</span><span class="nf">host</span><span class="p">(.</span><span class="nx">green</span><span class="p">)</span> <span class="nx">section</span><span class="p">,</span>
<span class="p">:</span><span class="nf">host</span><span class="p">([</span><span class="nx">type</span><span class="o">=</span><span class="dl">"</span><span class="s2">success</span><span class="dl">"</span><span class="p">])</span> <span class="nx">section</span> <span class="p">{</span>
<span class="nx">background</span><span class="o">-</span><span class="nx">color</span><span class="p">:</span> <span class="err">#</span><span class="mi">5</span><span class="nx">CB85C</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">:</span><span class="nf">host</span><span class="p">(.</span><span class="nx">blue</span><span class="p">)</span> <span class="nx">section</span><span class="p">,</span>
<span class="p">:</span><span class="nf">host</span><span class="p">([</span><span class="nx">type</span><span class="o">=</span><span class="dl">"</span><span class="s2">info</span><span class="dl">"</span><span class="p">])</span> <span class="nx">section</span> <span class="p">{</span>
<span class="nx">background</span><span class="o">-</span><span class="nx">color</span><span class="p">:</span> <span class="err">#</span><span class="mi">337</span><span class="nx">ab7</span><span class="p">;</span>
<span class="p">}</span>
<span class="nx">main</span> <span class="p">{</span>
<span class="nl">color</span><span class="p">:</span> <span class="nx">#fff</span><span class="p">;</span>
<span class="nx">background</span><span class="o">-</span><span class="nx">color</span><span class="p">:</span> <span class="nx">transparent</span><span class="p">;</span>
<span class="nx">font</span><span class="o">-</span><span class="nx">family</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Fira Sans</span><span class="dl">'</span><span class="p">,</span> <span class="nx">sans</span><span class="o">-</span><span class="nx">serif</span><span class="p">;</span>
<span class="nl">overflow</span><span class="p">:</span> <span class="nx">visible</span><span class="p">;</span>
<span class="nl">display</span><span class="p">:</span> <span class="nx">flex</span><span class="p">;</span>
<span class="nx">flex</span><span class="o">-</span><span class="nx">flow</span><span class="p">:</span> <span class="nx">row</span><span class="p">;</span>
<span class="nl">height</span><span class="p">:</span> <span class="mi">60</span><span class="nx">px</span><span class="p">;</span>
<span class="p">}</span>
<span class="nx">section</span> <span class="p">{</span>
<span class="nx">border</span><span class="o">-</span><span class="nx">radius</span><span class="p">:</span> <span class="mi">4</span><span class="nx">px</span><span class="p">;</span>
<span class="nx">margin</span><span class="o">-</span><span class="nx">right</span><span class="p">:</span> <span class="mi">2</span><span class="nx">px</span><span class="p">;</span>
<span class="nl">padding</span><span class="p">:</span> <span class="mi">0</span><span class="p">;</span>
<span class="nl">display</span><span class="p">:</span> <span class="nx">flex</span><span class="p">;</span>
<span class="nx">justify</span><span class="o">-</span><span class="nx">content</span><span class="p">:</span> <span class="nx">space</span><span class="o">-</span><span class="nx">around</span><span class="p">;</span>
<span class="nx">align</span><span class="o">-</span><span class="nx">items</span><span class="p">:</span> <span class="nx">center</span><span class="p">;</span>
<span class="nx">flex</span><span class="o">-</span><span class="nx">flow</span><span class="p">:</span> <span class="nx">row</span><span class="p">;</span>
<span class="p">}</span>
<span class="nx">section</span><span class="p">.</span><span class="nx">notice</span> <span class="p">{</span>
<span class="nl">flex</span><span class="p">:</span> <span class="mf">10.5</span> <span class="mi">10</span><span class="o">%</span><span class="p">;</span>
<span class="p">}</span>
<span class="nx">section</span><span class="p">.</span><span class="nx">action</span> <span class="p">{</span>
<span class="nl">flex</span><span class="p">:</span> <span class="mi">1</span> <span class="mi">10</span><span class="o">%</span><span class="p">;</span>
<span class="p">}</span>
<span class="nx">section</span><span class="p">.</span><span class="nx">action</span><span class="p">:</span><span class="nx">hover</span> <span class="p">{</span>
<span class="nl">cursor</span><span class="p">:</span> <span class="nx">pointer</span><span class="p">;</span>
<span class="p">}</span>
<span class="nx">aside</span> <span class="p">{</span>
<span class="nl">flex</span><span class="p">:</span> <span class="mi">1</span> <span class="mi">10</span><span class="o">%</span><span class="p">;</span>
<span class="nl">padding</span><span class="p">:</span> <span class="mi">6</span><span class="nx">px</span> <span class="mi">0</span> <span class="mi">0</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
<span class="nx">article</span> <span class="p">{</span>
<span class="nl">flex</span><span class="p">:</span> <span class="mi">9</span> <span class="mi">10</span><span class="o">%</span><span class="p">;</span>
<span class="p">}</span>
<span class="nx">div</span> <span class="p">{</span>
<span class="nl">flex</span><span class="p">:</span> <span class="mi">1</span> <span class="nx">auto</span><span class="p">;</span>
<span class="nx">align</span><span class="o">-</span><span class="nb">self</span><span class="p">:</span> <span class="nx">center</span><span class="p">;</span>
<span class="nx">text</span><span class="o">-</span><span class="nx">align</span><span class="p">:</span> <span class="nx">center</span><span class="p">;</span>
<span class="nl">padding</span><span class="p">:</span> <span class="mi">6</span><span class="nx">px</span> <span class="mi">0</span> <span class="mi">0</span> <span class="mi">0</span><span class="p">;</span>
<span class="p">}</span>
<span class="nx">div</span> <span class="p">.</span><span class="nx">close</span> <span class="p">{</span>
<span class="nx">text</span><span class="o">-</span><span class="nx">align</span><span class="p">:</span> <span class="nx">center</span><span class="p">;</span>
<span class="p">}</span>
<span class="o"><</span><span class="sr">/style</span><span class="err">>
</span> <span class="o"><</span><span class="nx">main</span> <span class="kd">class</span><span class="o">=</span><span class="dl">"</span><span class="s2">alert-box</span><span class="dl">"</span><span class="o">></span>
<span class="o"><</span><span class="nx">section</span> <span class="kd">class</span><span class="o">=</span><span class="dl">"</span><span class="s2">notice</span><span class="dl">"</span><span class="o">></span>
<span class="o"><</span><span class="nx">aside</span><span class="o">></span>
<span class="o"><</span><span class="nx">content</span> <span class="nx">select</span><span class="o">=</span><span class="dl">"</span><span class="s2">.icon</span><span class="dl">"</span><span class="o">><</span><span class="sr">/content</span><span class="err">>
</span> <span class="o"><</span><span class="sr">/aside</span><span class="err">>
</span> <span class="o"><</span><span class="nx">article</span><span class="o">></span>
<span class="o"><</span><span class="nx">content</span> <span class="nx">select</span><span class="o">=</span><span class="dl">"</span><span class="s2">.message</span><span class="dl">"</span><span class="o">><</span><span class="sr">/content</span><span class="err">>
</span> <span class="o"><</span><span class="sr">/article</span><span class="err">>
</span> <span class="o"><</span><span class="sr">/section</span><span class="err">>
</span> <span class="o"><</span><span class="nx">section</span> <span class="kd">class</span><span class="o">=</span><span class="dl">"</span><span class="s2">action</span><span class="dl">"</span><span class="o">></span>
<span class="o"><</span><span class="nx">div</span><span class="o">></span>
<span class="o"><</span><span class="nx">content</span> <span class="nx">select</span><span class="o">=</span><span class="dl">"</span><span class="s2">.close</span><span class="dl">"</span><span class="o">><</span><span class="sr">/content</span><span class="err">>
</span> <span class="o"><</span><span class="sr">/div</span><span class="err">>
</span> <span class="o"><</span><span class="sr">/section</span><span class="err">>
</span> <span class="o"><</span><span class="sr">/main</span><span class="err">>
</span><span class="o"><</span><span class="sr">/template</span><span class="err">>
</span><span class="o"><</span><span class="nx">script</span> <span class="nx">type</span><span class="o">=</span><span class="dl">"</span><span class="s2">text/javascript</span><span class="dl">"</span> <span class="nx">charset</span><span class="o">=</span><span class="dl">"</span><span class="s2">utf-8</span><span class="dl">"</span><span class="o">></span>
<span class="nb">document</span><span class="p">.</span><span class="nf">addEventListener</span><span class="p">(</span><span class="dl">'</span><span class="s1">DOMContentLoaded</span><span class="dl">'</span><span class="p">,</span> <span class="nf">function </span><span class="p">()</span> <span class="p">{</span>
<span class="dl">'</span><span class="s1">use-strict</span><span class="dl">'</span><span class="p">;</span>
<span class="nb">window</span><span class="p">.</span><span class="nx">AlertBoxElement</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nf">registerElement</span><span class="p">(</span><span class="dl">'</span><span class="s1">alert-box</span><span class="dl">'</span><span class="p">,</span> <span class="p">{</span>
<span class="na">prototype</span><span class="p">:</span> <span class="nb">Object</span><span class="p">.</span><span class="nf">create</span><span class="p">(</span><span class="nx">HTMLElement</span><span class="p">.</span><span class="nx">prototype</span><span class="p">,</span> <span class="p">{</span>
<span class="na">createdCallback</span><span class="p">:</span> <span class="p">{</span>
<span class="na">value</span><span class="p">:</span> <span class="nf">function </span><span class="p">()</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nf">addEventListener</span><span class="p">(</span><span class="dl">'</span><span class="s1">click</span><span class="dl">'</span><span class="p">,</span> <span class="k">this</span><span class="p">.</span><span class="nx">clickHandler</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">},</span>
<span class="na">attachedCallback</span><span class="p">:</span> <span class="p">{</span>
<span class="na">value</span><span class="p">:</span> <span class="nf">function </span><span class="p">()</span> <span class="p">{</span>
<span class="k">if </span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">innerHTML</span> <span class="o">!==</span> <span class="dl">''</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">template</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nf">getElementById</span><span class="p">(</span><span class="dl">'</span><span class="s1">alert-box</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">clone</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nf">importNode</span><span class="p">(</span><span class="nx">template</span><span class="p">.</span><span class="nx">content</span><span class="p">,</span> <span class="kc">true</span><span class="p">);</span>
<span class="k">this</span><span class="p">.</span><span class="nf">createShadowRoot</span><span class="p">().</span><span class="nf">appendChild</span><span class="p">(</span><span class="nx">clone</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">this</span><span class="p">.</span><span class="nx">classList</span><span class="p">.</span><span class="nf">add</span><span class="p">(</span><span class="dl">'</span><span class="s1">ready</span><span class="dl">'</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">},</span>
<span class="na">detachedCallback</span><span class="p">:</span> <span class="p">{</span>
<span class="na">value</span><span class="p">:</span> <span class="nf">function </span><span class="p">()</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nf">removeEventListener</span><span class="p">(</span><span class="dl">'</span><span class="s1">click</span><span class="dl">'</span><span class="p">,</span> <span class="k">this</span><span class="p">.</span><span class="nx">clickHandler</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">},</span>
<span class="na">clickHandler</span><span class="p">:</span> <span class="p">{</span>
<span class="na">value</span><span class="p">:</span> <span class="nf">function </span><span class="p">(</span><span class="nx">evt</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">evt</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">CustomEvent</span><span class="p">(</span><span class="dl">'</span><span class="s1">alert-box-click</span><span class="dl">'</span><span class="p">,</span> <span class="nx">evt</span><span class="p">);</span>
<span class="k">this</span><span class="p">.</span><span class="nf">dispatchEvent</span><span class="p">(</span><span class="nx">evt</span><span class="p">);</span>
<span class="k">this</span><span class="p">.</span><span class="nx">parentNode</span><span class="p">.</span><span class="nf">removeChild</span><span class="p">(</span><span class="k">this</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">})</span>
<span class="p">});</span>
<span class="p">});</span>
<span class="o"><</span><span class="sr">/script</span><span class="err">>
</span><span class="c"><!--</span>
<span class="nx">Web</span> <span class="nx">Component</span><span class="p">:</span> <span class="nx">Icon</span> <span class="nx">Info</span>
<span class="o">--></span>
<span class="o"><</span><span class="nx">template</span> <span class="nx">id</span><span class="o">=</span><span class="dl">"</span><span class="s2">info-icon</span><span class="dl">"</span><span class="o">></span>
<span class="o"><</span><span class="nx">svg</span> <span class="nx">version</span><span class="o">=</span><span class="dl">"</span><span class="s2">1.1</span><span class="dl">"</span> <span class="nx">xmlns</span><span class="o">=</span><span class="dl">"</span><span class="s2">http://www.w3.org/2000/svg</span><span class="dl">"</span> <span class="nx">xmlns</span><span class="p">:</span><span class="nx">xlink</span><span class="o">=</span><span class="dl">"</span><span class="s2">http://www.w3.org/1999/xlink</span><span class="dl">"</span>
<span class="nx">viewBox</span><span class="o">=</span><span class="dl">"</span><span class="s2">0 0 100 100</span><span class="dl">"</span> <span class="nx">enable</span><span class="o">-</span><span class="nx">background</span><span class="o">=</span><span class="dl">"</span><span class="s2">new 0 0 100 100</span><span class="dl">"</span> <span class="nx">xml</span><span class="p">:</span><span class="nx">space</span><span class="o">=</span><span class="dl">"</span><span class="s2">preserve</span><span class="dl">"</span><span class="o">></span>
<span class="o"><</span><span class="nx">g</span><span class="o">></span>
<span class="o"><</span><span class="nx">path</span> <span class="nx">fill</span><span class="o">=</span><span class="dl">"</span><span class="s2">#FFFFFF</span><span class="dl">"</span> <span class="nx">d</span><span class="o">=</span><span class="dl">"</span><span class="s2">M51.833,39.464c0,0.919-0.68,1.68-1.76,1.68c-1.04,0-1.72-0.76-1.72-1.68c0-0.92,0.68-1.68,1.72-1.68
C51.153,37.785,51.833,38.544,51.833,39.464z M48.954,67.899V46.983h2.32v20.917H48.954z</span><span class="dl">"</span><span class="o">/></span>
<span class="o"><</span><span class="sr">/g</span><span class="err">>
</span> <span class="o"><</span><span class="nx">circle</span> <span class="nx">fill</span><span class="o">=</span><span class="dl">"</span><span class="s2">none</span><span class="dl">"</span> <span class="nx">stroke</span><span class="o">=</span><span class="dl">"</span><span class="s2">#FFFFFF</span><span class="dl">"</span> <span class="nx">stroke</span><span class="o">-</span><span class="nx">width</span><span class="o">=</span><span class="dl">"</span><span class="s2">2</span><span class="dl">"</span> <span class="nx">stroke</span><span class="o">-</span><span class="nx">miterlimit</span><span class="o">=</span><span class="dl">"</span><span class="s2">10</span><span class="dl">"</span> <span class="nx">cx</span><span class="o">=</span><span class="dl">"</span><span class="s2">49.95</span><span class="dl">"</span> <span class="nx">cy</span><span class="o">=</span><span class="dl">"</span><span class="s2">50.198</span><span class="dl">"</span> <span class="nx">r</span><span class="o">=</span><span class="dl">"</span><span class="s2">29.416</span><span class="dl">"</span><span class="o">/></span>
<span class="o"><</span><span class="sr">/svg</span><span class="err">>
</span><span class="o"><</span><span class="sr">/template</span><span class="err">>
</span><span class="o"><</span><span class="nx">script</span> <span class="nx">type</span><span class="o">=</span><span class="dl">"</span><span class="s2">text/javascript</span><span class="dl">"</span> <span class="nx">charset</span><span class="o">=</span><span class="dl">"</span><span class="s2">utf-8</span><span class="dl">"</span><span class="o">></span>
<span class="nb">document</span><span class="p">.</span><span class="nf">addEventListener</span><span class="p">(</span><span class="dl">'</span><span class="s1">DOMContentLoaded</span><span class="dl">'</span><span class="p">,</span> <span class="nf">function </span><span class="p">()</span> <span class="p">{</span>
<span class="dl">'</span><span class="s1">use-strict</span><span class="dl">'</span><span class="p">;</span>
<span class="nb">window</span><span class="p">.</span><span class="nx">IconInfoElement</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nf">registerElement</span><span class="p">(</span><span class="dl">'</span><span class="s1">icon-info</span><span class="dl">'</span><span class="p">,</span> <span class="p">{</span>
<span class="na">prototype</span><span class="p">:</span> <span class="nb">Object</span><span class="p">.</span><span class="nf">create</span><span class="p">(</span><span class="nx">HTMLElement</span><span class="p">.</span><span class="nx">prototype</span><span class="p">,</span> <span class="p">{</span>
<span class="na">attachedCallback</span><span class="p">:</span> <span class="p">{</span>
<span class="na">value</span><span class="p">:</span> <span class="nf">function </span><span class="p">()</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">template</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nf">getElementById</span><span class="p">(</span><span class="dl">'</span><span class="s1">info-icon</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">clone</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nf">importNode</span><span class="p">(</span><span class="nx">template</span><span class="p">.</span><span class="nx">content</span><span class="p">,</span> <span class="kc">true</span><span class="p">);</span>
<span class="k">this</span><span class="p">.</span><span class="nf">createShadowRoot</span><span class="p">().</span><span class="nf">appendChild</span><span class="p">(</span><span class="nx">clone</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">},</span>
<span class="p">})</span>
<span class="p">});</span>
<span class="p">});</span>
<span class="o"><</span><span class="sr">/script</span><span class="err">>
</span><span class="c"><!--</span>
<span class="nx">Web</span> <span class="nx">Component</span><span class="p">:</span> <span class="nx">Icon</span> <span class="nx">X</span>
<span class="o">--></span>
<span class="o"><</span><span class="nx">template</span> <span class="nx">id</span><span class="o">=</span><span class="dl">"</span><span class="s2">icon-x</span><span class="dl">"</span><span class="o">></span>
<span class="o"><</span><span class="nx">svg</span> <span class="nx">version</span><span class="o">=</span><span class="dl">"</span><span class="s2">1.1</span><span class="dl">"</span> <span class="nx">xmlns</span><span class="o">=</span><span class="dl">"</span><span class="s2">http://www.w3.org/2000/svg</span><span class="dl">"</span> <span class="nx">xmlns</span><span class="p">:</span><span class="nx">xlink</span><span class="o">=</span><span class="dl">"</span><span class="s2">http://www.w3.org/1999/xlink</span><span class="dl">"</span>
<span class="nx">viewBox</span><span class="o">=</span><span class="dl">"</span><span class="s2">0 0 100 100</span><span class="dl">"</span> <span class="nx">enable</span><span class="o">-</span><span class="nx">background</span><span class="o">=</span><span class="dl">"</span><span class="s2">new 0 0 100 100</span><span class="dl">"</span> <span class="nx">xml</span><span class="p">:</span><span class="nx">space</span><span class="o">=</span><span class="dl">"</span><span class="s2">preserve</span><span class="dl">"</span><span class="o">></span>
<span class="o"><</span><span class="nx">g</span><span class="o">></span>
<span class="o"><</span><span class="nx">path</span> <span class="nx">fill</span><span class="o">=</span><span class="dl">"</span><span class="s2">#FFFFFF</span><span class="dl">"</span> <span class="nx">d</span><span class="o">=</span><span class="dl">"</span><span class="s2">M61.059,64.036L49.884,49.837L39.005,64.036h-3.058l12.35-15.459L37.182,35.303h3.176l9.585,11.972
l9.467-11.972h3.059L51.59,48.451l12.643,15.585H61.059z</span><span class="dl">"</span><span class="o">/></span>
<span class="o"><</span><span class="sr">/g</span><span class="err">>
</span> <span class="o"><</span><span class="sr">/svg</span><span class="err">>
</span><span class="o"><</span><span class="sr">/template</span><span class="err">>
</span><span class="o"><</span><span class="nx">script</span> <span class="nx">type</span><span class="o">=</span><span class="dl">"</span><span class="s2">text/javascript</span><span class="dl">"</span> <span class="nx">charset</span><span class="o">=</span><span class="dl">"</span><span class="s2">utf-8</span><span class="dl">"</span><span class="o">></span>
<span class="nb">document</span><span class="p">.</span><span class="nf">addEventListener</span><span class="p">(</span><span class="dl">'</span><span class="s1">DOMContentLoaded</span><span class="dl">'</span><span class="p">,</span> <span class="nf">function </span><span class="p">()</span> <span class="p">{</span>
<span class="dl">'</span><span class="s1">use-strict</span><span class="dl">'</span><span class="p">;</span>
<span class="nb">window</span><span class="p">.</span><span class="nx">IconXElement</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nf">registerElement</span><span class="p">(</span><span class="dl">'</span><span class="s1">icon-x</span><span class="dl">'</span><span class="p">,</span> <span class="p">{</span>
<span class="na">prototype</span><span class="p">:</span> <span class="nb">Object</span><span class="p">.</span><span class="nf">create</span><span class="p">(</span><span class="nx">HTMLElement</span><span class="p">.</span><span class="nx">prototype</span><span class="p">,</span> <span class="p">{</span>
<span class="na">attachedCallback</span><span class="p">:</span> <span class="p">{</span>
<span class="na">value</span><span class="p">:</span> <span class="nf">function </span><span class="p">()</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">template</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nf">getElementById</span><span class="p">(</span><span class="dl">'</span><span class="s1">icon-x</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">clone</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nf">importNode</span><span class="p">(</span><span class="nx">template</span><span class="p">.</span><span class="nx">content</span><span class="p">,</span> <span class="kc">true</span><span class="p">);</span>
<span class="k">this</span><span class="p">.</span><span class="nf">createShadowRoot</span><span class="p">().</span><span class="nf">appendChild</span><span class="p">(</span><span class="nx">clone</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">},</span>
<span class="p">})</span>
<span class="p">});</span>
<span class="p">});</span>
<span class="o"><</span><span class="sr">/script</span><span class="err">>
</span></code></pre>
<p>Upgraded Web Component to an Ember.Component...</p>
<pre class="highlight"><code><!--
Upgraded Web Component (to an Ember.Component) used within a template...
Ember Components utilizing native `<alert-box>` Web Component.
-->
<script type="text/x-handlebars" data-template-name="components/countdown-info">
<p class="message">Info Alert Box, Countdown is Over.</p>
<icon-info class="icon"></icon-info>
<icon-x class="close"></icon-x>
</script>
</code></pre>
<p>Ember.Component template for content of native template...</p>
<pre class="highlight"><code><script type="text/x-handlebars" data-template-name="components/countdown-warning">
<p class="message">
Warning Alert Box w/ Countdown:<br>
<span class="minutes">{{minutes}}</span> minutes
<span class="seconds">{{seconds}}</span> seconds.
</p>
<icon-warning class="icon"></icon-warning>
<icon-x class="close"></icon-x>
</script>
</code></pre>
<p>Ember.Component upgrade from Web Component...</p>
<pre class="highlight"><code><span class="c1">// AlertBoxComponents and components that extend it upgrade</span>
<span class="c1">// a native Web Component `<alert-box>`</span>
<span class="c1">// NOTE: without alert-box being defined in ember...</span>
<span class="c1">// The feature for `ember-htmlbars-component-generation: true`</span>
<span class="c1">// would bug out on `<alert-box>` used in a template</span>
<span class="nx">App</span><span class="p">.</span><span class="nx">AlertBoxComponent</span> <span class="o">=</span> <span class="nx">Ember</span><span class="p">.</span><span class="nx">Component</span><span class="p">.</span><span class="nf">extend</span><span class="p">({</span>
<span class="na">tagName</span><span class="p">:</span> <span class="dl">'</span><span class="s1">alert-box</span><span class="dl">'</span><span class="p">,</span>
<span class="na">attributeBindings</span><span class="p">:</span> <span class="p">[</span><span class="dl">'</span><span class="s1">type</span><span class="dl">'</span><span class="p">],</span>
<span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">info</span><span class="dl">'</span>
<span class="p">});</span>
<span class="nx">App</span><span class="p">.</span><span class="nx">CountdownWarningComponent</span> <span class="o">=</span> <span class="nx">App</span><span class="p">.</span><span class="nx">AlertBoxComponent</span><span class="p">.</span><span class="nf">extend</span><span class="p">({</span>
<span class="na">classNames</span><span class="p">:</span> <span class="p">[</span><span class="dl">'</span><span class="s1">fixed</span><span class="dl">'</span><span class="p">],</span>
<span class="na">attributeBindings</span><span class="p">:</span> <span class="p">[</span><span class="dl">'</span><span class="s1">minutes:data-minutes</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">seconds:data-seconds</span><span class="dl">'</span><span class="p">],</span>
<span class="na">layoutName</span><span class="p">:</span> <span class="dl">'</span><span class="s1">countdown-warning</span><span class="dl">'</span><span class="p">,</span>
<span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">warning</span><span class="dl">'</span><span class="p">,</span>
<span class="na">minutes</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
<span class="na">seconds</span><span class="p">:</span> <span class="mi">0</span>
<span class="p">});</span>
<span class="nx">App</span><span class="p">.</span><span class="nx">CountdownInfoComponent</span> <span class="o">=</span> <span class="nx">App</span><span class="p">.</span><span class="nx">AlertBoxComponent</span><span class="p">.</span><span class="nf">extend</span><span class="p">({</span>
<span class="na">classNames</span><span class="p">:</span> <span class="p">[</span><span class="dl">'</span><span class="s1">fixed</span><span class="dl">'</span><span class="p">],</span>
<span class="na">layoutName</span><span class="p">:</span> <span class="dl">'</span><span class="s1">countdown-info</span><span class="dl">'</span>
<span class="p">});</span>
</code></pre>
<p>Ember.Component only (not using the Web Component)...</p>
<pre class="highlight"><code><script type="text/x-handlebars" data-template-name="components/alert-box">
<div class="notice box">
<div class="left">
{{partial iconTemplateName}}
</div>
<div class="middle">
<p class="message">
{{yield}}
</p>
</div>
</div>
<div class="action box">
<div class="right">
{{#if hasCloseContent}}
{{closeContent}}
{{else}}
{{partial closeTemplateName}}
{{/if}}
</div>
</div>
</script>
</code></pre>
<h2>I'm betting Ember and Web Components…</h2>
<p>I'm really happy with the Shadow Dom and the encapsultation it provides for DOM elements. However, since I can't use that across the board I'm stoked that Ember Components do follow the general patterns for native Web Component. </p>
<p>I can use a custom <code>tagName</code> and benefit from using a custom HTML element. And write CSS that provides the default styles for that Custom Element. Even though the Ember Component does not encapsultate the component's DOM like Shadow DOM does using a shadow root, at least the Ember Component has an isolated scope.</p>
<p>I enjoyed test driving Web Components by authoring an Alert Box Web Component, and was able to borrow from that experiment code that is projection ready, the custom element styles and markup.</p>
<p>I am looking forward to the specification for Web Components taking shape and being implemented in more browsers than just Chrome… so I'm placing my bets that Web Components and Ember Components will live in synchronicity at some point in the near future. Web Components doing all the things they are designed to do, working together with Ember Components providing a simple way to stamp out Web Components and provide some useful data bindings as well as work together, perhaps with Custom Events.</p>
<h2>Thanks to…</h2>
<ul>
<li>W3C for publishing the <a href="proxy.php?url=http://www.w3.org/TR/#tr_Web_Components" target="_blank" rel="noopener noreferrer">Web Component</a> specs</li>
<li><a href="proxy.php?url=http://www.html5rocks.com/en/search?q=Web+Components" target="_blank" rel="noopener noreferrer">HTMLRocks Tutorials</a> for publishing great tutorials on Web Components</li>
</ul>
<p>To provide a brief overview of Web Components, I borrowed the content from the two sources above to provide a short summary as highlights of the specifications under the Web Component umbrella.</p>
Measuring Performance with User Timing API, in an Ember Applicationhttps://pixelhandler.dev/posts/measuring-performance-with-user-timing-api-in-an-ember-application2015-03-01T00:00:00+00:002015-03-01T00:00:00+00:00Billy HeatonFor more info on Handlebars and HTMLbars see:
[Compiling Templates in Ember v1.10.0]
[Feature-by-Feature Review]<p>For more info on Handlebars and HTMLbars see:<br>
* <a href="proxy.php?url=http://emberjs.com/blog/2015/02/05/compiling-templates-in-1-10-0.html" target="_blank" rel="noopener noreferrer">Compiling Templates in Ember v1.10.0</a><br>
* <a href="proxy.php?url=http://colintoh.com/blog/htmlbars" target="_blank" rel="noopener noreferrer">Feature-by-Feature Review</a></p>
<h2>Using the renderTemplate hook to mark, measure and report metrics</h2>
<p>In an Ember application, there are various hooks between <a href="proxy.php?url=http://emberjs.com/guides/routing/" target="_blank" rel="noopener noreferrer">routing</a> a request and drawing HTML on the DOM. The <a href="proxy.php?url=http://emberjs.com/api/classes/Ember.Route.html#method_renderTemplate" target="_blank" rel="noopener noreferrer">renderTemplate</a> hook is the most desireable hook to utilize for marking, measuring and reporting the performance metrics captured while presenting HTML to users. The <code>renderTemplate</code> hook is called after the <code>model</code> hooks and <code>setupController</code> hook compose your data for your template bindings. Combined with the <a href="proxy.php?url=http://emberjs.com/guides/understanding-ember/run-loop/" target="_blank" rel="noopener noreferrer">run-loop</a>, specifically the <a href="proxy.php?url=http://emberjs.com/api/classes/Ember.run.html#method_schedule" target="_blank" rel="noopener noreferrer">schedule</a> for <code>afterRender</code> the <code>renderTemplate</code> hook can mark the <em>beginning</em> of the work to render a template and, in the <code>afterRender</code> queue, mark the <em>completion</em> of the work. (<code>Ember.run.queues</code> lists the queues that are scheduled by the run loop: <code>["sync", "actions", "routerTransitions", "render", "afterRender", "destroy"]</code>)</p>
<h2>Mark, Measure and Report User Timing Metrics</h2>
<p>I created a utility with an interface to <a href="proxy.php?url=http://www.w3.org/TR/user-timing/#performancemark" target="_blank" rel="noopener noreferrer">mark</a>, <a href="proxy.php?url=http://www.w3.org/TR/user-timing/#performancemeasure" target="_blank" rel="noopener noreferrer">measure</a> and report measurements taken with the <a href="proxy.php?url=http://www.w3.org/TR/user-timing" target="_blank" rel="noopener noreferrer">User Timing</a> API. I did use a polyfill for browsers that do not implement the <a href="proxy.php?url=http://www.w3.org/standards/techs/performance#w3c_all" target="_blank" rel="noopener noreferrer">performance</a> specifications.</p>
<p>I used the mixin below in various routes, e.g. Index, Application, Post etc. to collect and measure the statistics for reporting on the timings that an Ember application renders HTML.</p>
<pre class="highlight"><code><span class="k">import</span> <span class="nx">Ember</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">ember</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="nx">config</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../config/environment</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">mark</span><span class="p">,</span> <span class="nx">measure</span><span class="p">,</span> <span class="nx">report</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../utils/metrics</span><span class="dl">'</span><span class="p">;</span>
<span class="k">export</span> <span class="k">default</span> <span class="nx">Ember</span><span class="p">.</span><span class="nx">Mixin</span><span class="p">.</span><span class="nf">create</span><span class="p">({</span>
<span class="na">measurementName</span><span class="p">:</span> <span class="nx">Ember</span><span class="p">.</span><span class="nx">required</span><span class="p">,</span>
<span class="na">reportUserTimings</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="nf">renderTemplate</span><span class="p">(</span><span class="nx">controller</span><span class="p">,</span> <span class="nx">model</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">beginName</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">mark_begin_rendering_</span><span class="dl">'</span> <span class="o">+</span> <span class="k">this</span><span class="p">.</span><span class="nx">measurementName</span><span class="p">;</span>
<span class="kd">var</span> <span class="nx">endName</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">mark_end_rendering_</span><span class="dl">'</span> <span class="o">+</span> <span class="k">this</span><span class="p">.</span><span class="nx">measurementName</span><span class="p">;</span>
<span class="k">if </span><span class="p">(</span><span class="nx">config</span><span class="p">.</span><span class="nx">APP</span><span class="p">.</span><span class="nx">REPORT_METRICS</span><span class="p">)</span> <span class="p">{</span>
<span class="nf">mark</span><span class="p">(</span><span class="nx">beginName</span><span class="p">);</span>
<span class="nx">Ember</span><span class="p">.</span><span class="nx">run</span><span class="p">.</span><span class="nf">scheduleOnce</span><span class="p">(</span><span class="dl">'</span><span class="s1">afterRender</span><span class="dl">'</span><span class="p">,</span> <span class="k">this</span><span class="p">,</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
<span class="nf">mark</span><span class="p">(</span><span class="nx">endName</span><span class="p">);</span>
<span class="nf">measure</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">measurementName</span><span class="p">,</span> <span class="nx">beginName</span><span class="p">,</span> <span class="nx">endName</span><span class="p">);</span>
<span class="k">if </span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">reportUserTimings</span><span class="p">)</span> <span class="p">{</span>
<span class="nf">report</span><span class="p">();</span>
<span class="p">}</span>
<span class="p">});</span>
<span class="p">}</span>
<span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nf">_super</span><span class="p">(</span><span class="nx">controller</span><span class="p">,</span> <span class="nx">model</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">});</span>
</code></pre>
<h2>Utility Module for Metrics Collection and Reporting</h2>
<p>This mixin wraps the <code>window.performance</code> methods and provides functions for reporting the measurements using an XHR request and <a href="proxy.php?url=https://developers.google.com/analytics/devguides/collection/analyticsjs/user-timings" target="_blank" rel="noopener noreferrer">Google Analytics to report User Timings</a>.</p>
<p>What is amazing about the performance measurements is that the API uses high-resolution timing with sub-millisecond resolution.</p>
<pre class="highlight"><code><span class="cm">/*jshint unused:false*/</span>
<span class="k">import</span> <span class="nx">Ember</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">ember</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="nx">config</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../config/environment</span><span class="dl">'</span><span class="p">;</span>
<span class="k">export</span> <span class="kd">function</span> <span class="nf">mark</span><span class="p">(</span><span class="nx">name</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if </span><span class="p">(</span><span class="o">!</span><span class="nb">window</span><span class="p">.</span><span class="nx">performance</span> <span class="o">||</span> <span class="o">!</span><span class="nb">window</span><span class="p">.</span><span class="nx">performance</span><span class="p">.</span><span class="nx">mark</span> <span class="p">)</span> <span class="p">{</span> <span class="k">return</span><span class="p">;</span> <span class="p">}</span>
<span class="nb">window</span><span class="p">.</span><span class="nx">performance</span><span class="p">.</span><span class="nf">mark</span><span class="p">(</span><span class="nx">name</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">export</span> <span class="kd">function</span> <span class="nf">measure</span><span class="p">(</span><span class="nx">name</span><span class="p">,</span> <span class="nx">begin</span><span class="p">,</span> <span class="nx">end</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if </span><span class="p">(</span><span class="o">!</span><span class="nb">window</span><span class="p">.</span><span class="nx">performance</span> <span class="o">||</span> <span class="o">!</span><span class="nb">window</span><span class="p">.</span><span class="nx">performance</span><span class="p">.</span><span class="nx">measure</span> <span class="p">)</span> <span class="p">{</span> <span class="k">return</span><span class="p">;</span> <span class="p">}</span>
<span class="nb">window</span><span class="p">.</span><span class="nx">performance</span><span class="p">.</span><span class="nf">measure</span><span class="p">(</span><span class="nx">name</span><span class="p">,</span> <span class="nx">begin</span><span class="p">,</span> <span class="nx">end</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">export</span> <span class="kd">function</span> <span class="nf">appReady</span><span class="p">()</span> <span class="p">{</span>
<span class="k">return</span> <span class="nf">measureEntry</span><span class="p">(</span><span class="dl">'</span><span class="s1">app_ready</span><span class="dl">'</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">export</span> <span class="kd">function</span> <span class="nf">appUnload</span><span class="p">()</span> <span class="p">{</span>
<span class="k">return</span> <span class="nf">measureEntry</span><span class="p">(</span><span class="dl">'</span><span class="s1">app_unload</span><span class="dl">'</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">export</span> <span class="kd">function</span> <span class="nf">pageView</span><span class="p">()</span> <span class="p">{</span>
<span class="k">return</span> <span class="nf">measureEntry</span><span class="p">(</span><span class="dl">'</span><span class="s1">page_view</span><span class="dl">'</span><span class="p">);</span>
<span class="p">}</span>
<span class="kd">function</span> <span class="nf">measureEntry</span><span class="p">(</span><span class="nx">name</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if </span><span class="p">(</span><span class="o">!</span><span class="nb">window</span><span class="p">.</span><span class="nx">performance</span> <span class="o">||</span> <span class="o">!</span><span class="nb">window</span><span class="p">.</span><span class="nx">performance</span><span class="p">.</span><span class="nx">getEntriesByName</span> <span class="p">)</span> <span class="p">{</span> <span class="k">return</span><span class="p">;</span> <span class="p">}</span>
<span class="kd">var</span> <span class="nx">markName</span> <span class="o">=</span> <span class="nx">name</span> <span class="o">+</span> <span class="dl">'</span><span class="s1">_now</span><span class="dl">'</span><span class="p">;</span>
<span class="nf">mark</span><span class="p">(</span><span class="nx">markName</span><span class="p">);</span>
<span class="k">if </span><span class="p">(</span><span class="nb">window</span><span class="p">.</span><span class="nx">performance</span><span class="p">.</span><span class="nx">timing</span><span class="p">)</span> <span class="p">{</span>
<span class="nf">measure</span><span class="p">(</span><span class="nx">name</span><span class="p">,</span> <span class="dl">'</span><span class="s1">navigationStart</span><span class="dl">'</span><span class="p">,</span> <span class="nx">markName</span><span class="p">);</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="nf">measure</span><span class="p">(</span><span class="nx">name</span><span class="p">,</span> <span class="nx">markName</span><span class="p">,</span> <span class="nx">markName</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">export</span> <span class="kd">function</span> <span class="nf">report</span><span class="p">()</span> <span class="p">{</span>
<span class="k">if </span><span class="p">(</span><span class="o">!</span><span class="nb">window</span><span class="p">.</span><span class="nx">performance</span> <span class="o">||</span> <span class="o">!</span><span class="nb">window</span><span class="p">.</span><span class="nx">performance</span><span class="p">.</span><span class="nx">getEntriesByType</span> <span class="p">)</span> <span class="p">{</span> <span class="k">return</span><span class="p">;</span> <span class="p">}</span>
<span class="nb">window</span><span class="p">.</span><span class="nf">setTimeout</span><span class="p">(</span><span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
<span class="nf">send</span><span class="p">(</span><span class="nb">window</span><span class="p">.</span><span class="nx">performance</span><span class="p">.</span><span class="nf">getEntriesByType</span><span class="p">(</span><span class="dl">'</span><span class="s1">measure</span><span class="dl">'</span><span class="p">));</span>
<span class="nf">clear</span><span class="p">();</span>
<span class="p">},</span> <span class="mi">1000</span><span class="p">);</span>
<span class="p">}</span>
<span class="kd">function</span> <span class="nf">send</span><span class="p">(</span><span class="nx">measurements</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">measurement</span><span class="p">;</span>
<span class="k">for </span><span class="p">(</span><span class="kd">var</span> <span class="nx">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="o"><</span> <span class="nx">measurements</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span> <span class="o">++</span><span class="nx">i</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">measurement</span> <span class="o">=</span> <span class="nx">measurements</span><span class="p">[</span><span class="nx">i</span><span class="p">];</span>
<span class="nf">post</span><span class="p">(</span><span class="nx">measurement</span><span class="p">);</span>
<span class="nf">gaTrackTiming</span><span class="p">(</span><span class="nx">measurement</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">export</span> <span class="kd">function</span> <span class="nf">post</span><span class="p">(</span><span class="nx">measurement</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">payload</span> <span class="o">=</span> <span class="nf">createMetric</span><span class="p">(</span><span class="nx">measurement</span><span class="p">);</span>
<span class="k">return</span> <span class="nx">Ember</span><span class="p">.</span><span class="nx">$</span><span class="p">.</span><span class="nf">ajax</span><span class="p">({</span>
<span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">POST</span><span class="dl">'</span><span class="p">,</span>
<span class="na">url</span><span class="p">:</span> <span class="nf">endpointUri</span><span class="p">(</span><span class="dl">'</span><span class="s1">metrics</span><span class="dl">'</span><span class="p">),</span>
<span class="na">contentType</span><span class="p">:</span> <span class="dl">'</span><span class="s1">application/json; charset=utf-8</span><span class="dl">'</span><span class="p">,</span>
<span class="na">data</span><span class="p">:</span> <span class="nx">JSON</span><span class="p">.</span><span class="nf">stringify</span><span class="p">({</span> <span class="na">metrics</span><span class="p">:</span> <span class="nx">payload</span> <span class="p">}),</span>
<span class="na">dataType</span><span class="p">:</span> <span class="dl">'</span><span class="s1">json</span><span class="dl">'</span>
<span class="p">});</span>
<span class="p">}</span>
<span class="kd">function</span> <span class="nf">gaTrackTiming</span><span class="p">(</span><span class="nx">measurement</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if </span><span class="p">(</span><span class="k">typeof</span> <span class="nb">window</span><span class="p">.</span><span class="nx">ga</span> <span class="o">!==</span> <span class="dl">'</span><span class="s1">function</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span> <span class="k">return</span><span class="p">;</span> <span class="p">}</span>
<span class="nb">window</span><span class="p">.</span><span class="nf">ga</span><span class="p">(</span><span class="dl">'</span><span class="s1">send</span><span class="dl">'</span><span class="p">,</span> <span class="p">{</span>
<span class="dl">'</span><span class="s1">hitType</span><span class="dl">'</span><span class="p">:</span> <span class="dl">'</span><span class="s1">timing</span><span class="dl">'</span><span class="p">,</span>
<span class="dl">'</span><span class="s1">timingCategory</span><span class="dl">'</span><span class="p">:</span> <span class="dl">'</span><span class="s1">user_timing</span><span class="dl">'</span><span class="p">,</span>
<span class="dl">'</span><span class="s1">timingVar</span><span class="dl">'</span><span class="p">:</span> <span class="nx">measurement</span><span class="p">.</span><span class="nx">name</span><span class="p">,</span>
<span class="dl">'</span><span class="s1">timingValue</span><span class="dl">'</span><span class="p">:</span> <span class="nx">measurement</span><span class="p">.</span><span class="nx">duration</span><span class="p">,</span>
<span class="dl">'</span><span class="s1">timingLabel</span><span class="dl">'</span><span class="p">:</span> <span class="nx">measurement</span><span class="p">.</span><span class="nx">emberVersion</span><span class="p">,</span>
<span class="dl">'</span><span class="s1">page</span><span class="dl">'</span><span class="p">:</span> <span class="nx">measurement</span><span class="p">.</span><span class="nx">pathname</span>
<span class="p">});</span>
<span class="p">}</span>
<span class="kd">function</span> <span class="nf">createMetric</span><span class="p">(</span><span class="nx">measurement</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="p">{</span>
<span class="na">date</span><span class="p">:</span> <span class="nb">Date</span><span class="p">.</span><span class="nf">now</span><span class="p">(),</span>
<span class="na">name</span><span class="p">:</span> <span class="nx">measurement</span><span class="p">.</span><span class="nx">name</span><span class="p">,</span>
<span class="na">pathname</span><span class="p">:</span> <span class="nx">location</span><span class="p">.</span><span class="nx">pathname</span><span class="p">,</span>
<span class="na">startTime</span><span class="p">:</span> <span class="nb">Math</span><span class="p">.</span><span class="nf">round</span><span class="p">(</span><span class="nx">measurement</span><span class="p">.</span><span class="nx">startTime</span><span class="p">),</span>
<span class="na">duration</span><span class="p">:</span> <span class="nc">Number</span><span class="p">(</span><span class="nb">Math</span><span class="p">.</span><span class="nf">round</span><span class="p">(</span><span class="nx">measurement</span><span class="p">.</span><span class="nx">duration</span> <span class="o">+</span> <span class="dl">'</span><span class="s1">e3</span><span class="dl">'</span><span class="p">)</span> <span class="o">+</span> <span class="dl">'</span><span class="s1">e-3</span><span class="dl">'</span><span class="p">),</span> <span class="c1">// round to thousandths</span>
<span class="na">visitor</span><span class="p">:</span> <span class="nb">window</span><span class="p">.</span><span class="nx">localStorage</span><span class="p">.</span><span class="nf">getItem</span><span class="p">(</span><span class="dl">'</span><span class="s1">visitor</span><span class="dl">'</span><span class="p">),</span>
<span class="na">screenWidth</span><span class="p">:</span> <span class="nb">window</span><span class="p">.</span><span class="nx">screen</span><span class="p">.</span><span class="nx">width</span><span class="p">,</span>
<span class="na">screenHeight</span><span class="p">:</span> <span class="nb">window</span><span class="p">.</span><span class="nx">screen</span><span class="p">.</span><span class="nx">height</span><span class="p">,</span>
<span class="na">screenColorDepth</span><span class="p">:</span> <span class="nb">window</span><span class="p">.</span><span class="nx">screen</span><span class="p">.</span><span class="nx">colorDepth</span><span class="p">,</span>
<span class="na">screenPixelDepth</span><span class="p">:</span> <span class="nb">window</span><span class="p">.</span><span class="nx">screen</span><span class="p">.</span><span class="nx">pixelDepth</span><span class="p">,</span>
<span class="na">screenOrientation</span><span class="p">:</span> <span class="p">(</span><span class="nb">window</span><span class="p">.</span><span class="nx">screen</span><span class="p">.</span><span class="nx">orientation</span><span class="p">)</span> <span class="p">?</span> <span class="nb">window</span><span class="p">.</span><span class="nx">screen</span><span class="p">.</span><span class="nx">orientation</span><span class="p">.</span><span class="nx">type</span> <span class="p">:</span> <span class="kc">null</span><span class="p">,</span>
<span class="na">blogVersion</span><span class="p">:</span> <span class="nx">config</span><span class="p">.</span><span class="nx">APP</span><span class="p">.</span><span class="nx">version</span><span class="p">,</span>
<span class="na">emberVersion</span><span class="p">:</span> <span class="nx">Ember</span><span class="p">.</span><span class="nx">VERSION</span><span class="p">,</span>
<span class="na">adapterType</span><span class="p">:</span> <span class="p">(</span><span class="nx">config</span><span class="p">.</span><span class="nx">APP</span><span class="p">.</span><span class="nx">USE_SOCKET_ADAPTER</span><span class="p">)</span> <span class="p">?</span> <span class="dl">'</span><span class="s1">SOCKET</span><span class="dl">'</span> <span class="p">:</span> <span class="dl">'</span><span class="s1">JSONAPI</span><span class="dl">'</span>
<span class="p">};</span>
<span class="p">}</span>
<span class="kd">function</span> <span class="nf">endpointUri</span><span class="p">(</span><span class="nx">resource</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">host</span> <span class="o">=</span> <span class="nx">config</span><span class="p">.</span><span class="nx">APP</span><span class="p">.</span><span class="nx">API_HOST</span><span class="p">;</span>
<span class="kd">var</span> <span class="nx">path</span> <span class="o">=</span> <span class="nx">config</span><span class="p">.</span><span class="nx">APP</span><span class="p">.</span><span class="nx">API_PATH</span><span class="p">;</span>
<span class="kd">var</span> <span class="nx">uri</span> <span class="o">=</span> <span class="p">(</span><span class="nx">path</span><span class="p">)</span> <span class="p">?</span> <span class="nx">host</span> <span class="o">+</span> <span class="dl">'</span><span class="s1">/</span><span class="dl">'</span> <span class="o">+</span> <span class="nx">path</span> <span class="p">:</span> <span class="nx">host</span><span class="p">;</span>
<span class="k">return</span> <span class="nx">uri</span> <span class="o">+</span> <span class="dl">'</span><span class="s1">/</span><span class="dl">'</span> <span class="o">+</span> <span class="nx">resource</span><span class="p">;</span>
<span class="p">}</span>
<span class="kd">function</span> <span class="nf">clear</span><span class="p">()</span> <span class="p">{</span>
<span class="nb">window</span><span class="p">.</span><span class="nx">performance</span><span class="p">.</span><span class="nf">clearMarks</span><span class="p">();</span>
<span class="nb">window</span><span class="p">.</span><span class="nx">performance</span><span class="p">.</span><span class="nf">clearMeasures</span><span class="p">();</span>
<span class="p">}</span>
</code></pre>
<h3>Learn more about User Timing API to Measure Performance</h3>
<ul>
<li><a href="proxy.php?url=http://www.w3.org/TR/#tr_Web_Performance" target="_blank" rel="noopener noreferrer">W3C Web Performance</a></li>
<li><a href="proxy.php?url=http://www.w3.org/TR/user-timing/#dom-performance-mark" target="_blank" rel="noopener noreferrer">Performance Mark Method</a></li>
<li><a href="proxy.php?url=http://www.w3.org/TR/user-timing/#dom-performance-measure" target="_blank" rel="noopener noreferrer">Performance Measure Method</a></li>
<li><a href="proxy.php?url=http://www.html5rocks.com/en/tutorials/webperformance/usertiming/" target="_blank" rel="noopener noreferrer">HTML5Rocks: User Timing API</a></li>
<li><a href="proxy.php?url=http://caniuse.com/#search=performance" target="_blank" rel="noopener noreferrer">Caniuse Performance</a></li>
<li><a href="proxy.php?url=http://www.w3.org/TR/hr-time/" target="_blank" rel="noopener noreferrer">High Resolution Time</a></li>
</ul>
<h2>Reporting the metrics</h2>
<p>At first I decided to add a <em>/metrics</em> endpoint to my API, then later realized that Google Analytics can collect and report on User Timing measurements as well (I'm using both). I prefer the data in my own database, I can create very specific queries to search the userAgent string to compare measurements for mobile, iPad, iPhone, etc.</p>
<p>In addition to collecting measurements for rendering templates I collected metrics on finding data (via an adapter). The metrics I report on are named <code>app_ready</code>, <code>application_view</code>, <code>index_view</code>, <code>post_view</code>, <code>page_view</code>, <code>app_unload</code>, <code>archive_view</code>, and <code>find_posts</code>.</p>
<p><em>An Example Metric Resource:</em></p>
<pre class="highlight"><code><span class="p">{</span><span class="w">
</span><span class="err">adapterType:</span><span class="w"> </span><span class="err">'SOCKET'</span><span class="p">,</span><span class="w">
</span><span class="err">blogVersion:</span><span class="w"> </span><span class="err">'</span><span class="mf">3.3</span><span class="err">.</span><span class="mi">8</span><span class="err">.fa</span><span class="mi">3</span><span class="err">fbaf</span><span class="mi">8</span><span class="err">'</span><span class="p">,</span><span class="w">
</span><span class="err">date:</span><span class="w"> </span><span class="err">'</span><span class="mi">2015-02-08</span><span class="err">T</span><span class="mi">06</span><span class="err">:</span><span class="mi">29</span><span class="err">:</span><span class="mf">17.368</span><span class="err">Z'</span><span class="p">,</span><span class="w">
</span><span class="err">duration:</span><span class="w"> </span><span class="mf">113.35</span><span class="p">,</span><span class="w">
</span><span class="err">emberVersion:</span><span class="w"> </span><span class="err">'</span><span class="mf">1.10</span><span class="err">.</span><span class="mi">0</span><span class="err">'</span><span class="p">,</span><span class="w">
</span><span class="err">id:</span><span class="w"> </span><span class="err">'</span><span class="mi">1</span><span class="err">a</span><span class="mi">9864</span><span class="err">a</span><span class="mi">2-4011-49</span><span class="err">d</span><span class="mi">8-9233</span><span class="err">-fa</span><span class="mi">76</span><span class="err">d</span><span class="mi">26e9040</span><span class="err">'</span><span class="p">,</span><span class="w">
</span><span class="err">name:</span><span class="w"> </span><span class="err">'post_view'</span><span class="p">,</span><span class="w">
</span><span class="err">pathname:</span><span class="w"> </span><span class="err">'/posts/refreshed-my-blog-with-express-and-emberjs'</span><span class="p">,</span><span class="w">
</span><span class="err">screenColorDepth:</span><span class="w"> </span><span class="mi">24</span><span class="p">,</span><span class="w">
</span><span class="err">screenHeight:</span><span class="w"> </span><span class="mi">900</span><span class="p">,</span><span class="w">
</span><span class="err">screenOrientation:</span><span class="w"> </span><span class="err">'landscape-primary'</span><span class="p">,</span><span class="w">
</span><span class="err">screenPixelDepth:</span><span class="w"> </span><span class="mi">24</span><span class="p">,</span><span class="w">
</span><span class="err">screenWidth:</span><span class="w"> </span><span class="mi">1440</span><span class="p">,</span><span class="w">
</span><span class="err">startTime:</span><span class="w"> </span><span class="mi">391</span><span class="p">,</span><span class="w">
</span><span class="err">userAgent:</span><span class="w"> </span><span class="err">'Mozilla/</span><span class="mf">5.0</span><span class="w"> </span><span class="err">(Macintosh;</span><span class="w"> </span><span class="err">Intel</span><span class="w"> </span><span class="err">Mac</span><span class="w"> </span><span class="err">OS</span><span class="w"> </span><span class="err">X</span><span class="w"> </span><span class="mi">10</span><span class="err">_</span><span class="mi">10</span><span class="err">_</span><span class="mi">2</span><span class="err">)</span><span class="w"> </span><span class="err">....</span><span class="w"> </span><span class="err">Safari/</span><span class="mf">537.36</span><span class="err">'</span><span class="p">,</span><span class="w">
</span><span class="err">visit:</span><span class="w"> </span><span class="err">'</span><span class="mi">22</span><span class="err">d</span><span class="mi">5</span><span class="err">d</span><span class="mi">2</span><span class="err">b</span><span class="mi">0</span><span class="err">be</span><span class="mi">4067</span><span class="err">ac</span><span class="mi">3</span><span class="err">af</span><span class="mi">57</span><span class="err">f</span><span class="mi">4</span><span class="err">b</span><span class="mi">14</span><span class="err">fcbb</span><span class="mi">4</span><span class="err">b</span><span class="mi">4</span><span class="err">d</span><span class="mi">89</span><span class="err">fe</span><span class="mi">34</span><span class="err">b</span><span class="mi">1</span><span class="err">eeeaab</span><span class="mi">910</span><span class="err">c</span><span class="mi">259e7</span><span class="err">b</span><span class="mi">9</span><span class="err">c</span><span class="mi">55</span><span class="err">aa'</span><span class="p">,</span><span class="w">
</span><span class="err">visitor:</span><span class="w"> </span><span class="err">'c</span><span class="mi">56</span><span class="err">f</span><span class="mi">439e-646</span><span class="err">b</span><span class="mi">-4</span><span class="err">f</span><span class="mi">6</span><span class="err">a-b</span><span class="mi">91</span><span class="err">b</span><span class="mi">-5</span><span class="err">c</span><span class="mi">119e21</span><span class="err">efba'</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre>
<p>I created endpoints on my API for <em>/metrics/impressions</em> and <em>/metrics/durations</em> that accept queries that use regular expression matching to search for data based on the object properties of the metrics. For example I can search the pathname, userAgent and emberVersion when reporting on the metrics data.</p>
<p>More info on these endpoints:</p>
<ul>
<li><a href="proxy.php?url=http://discuss.emberjs.com/t/collecting-performance-metrics-using-rendertemplate-hook-and-user-timing-api/7270/1" target="_blank" rel="noopener noreferrer">discuss.emberjs : Collecting performance metrics</a></li>
<li><a href="proxy.php?url=https://github.com/pixelhandler/blog/tree/master/server#metrics-api-endpoints" target="_blank" rel="noopener noreferrer">Metrics API Endpoints</a></li>
</ul>
<h2>Metrics: Rendering with HTMLBars vs Handlebars</h2>
<p>I consider the fact that the user only needs a single full screen at a time. So my app loads records in chunks when the user scrolls to the end. I'm not as interested in how well a large table of a thousand records performs; I don't think that users will enjoy waiting for the data anyway. But, for those interested in that kind of pain… I created a page on my site that has 1,000 metrics records, a long list. I collect metrics on rendering that metrics table as well. I'm not convinced that this metric really matters to users. Maybe, if I had a complex data visualization that had a 1,000 data points it would matter. Well, then I'd likely use a data vizualization library (like D3) to render the data instead. So, I'm not convinced that rendering a long list is really of any value to any user. Whether that (long list) measurement is relevant as metric for comparing a template library, my vote is thumbs down.</p>
<p>I am convinced that measuring the perceived performance of web application matters to my users. I think that with these measurements, I can identify trends and make some informed assumptions about how these metrics apply to other Ember applications I work on.</p>
<p>Below are some measurements I took, I gave about a week of web traffic to both template library solutions. I measured the timings for rendering views in my app running both a) Ember.js v1.8.1 with Handlebars v1.3 and b) Ember v1.10.0 and HTMLBars (the initial release).</p>
<p>I was able to collect a good sized data set, so that the average (speed) durations are good comparison between various facets of the metrics data, e.g. emberVersion and userAgent. I can report on the average of all user agents, or on a specfic type of userAgent. Since the timings are measured after data is fetched and the application is ready, I am confident that the metrics captured do reflect the timings that actual users experience.</p>
<h3>A few comparisons (Ember.js v1.10.0 and v1.8.1)</h3>
<p>The measurements below highlight some gains and losses as a result of the upgrade from Handlebars to HTMLbars in my application…</p>
<h4>All userAgents, desktop and mobile</h4>
<p>Index (home) page in v1.8.1 </p>
<pre class="highlight"><code><span class="err">//Ember</span><span class="w"> </span><span class="err">v</span><span class="mf">1.8</span><span class="err">.</span><span class="mi">1</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="err">average:</span><span class="w"> </span><span class="mf">154.41764912280703</span><span class="p">,</span><span class="w">
</span><span class="err">durations:</span><span class="w"> </span><span class="mi">114</span><span class="p">,</span><span class="w">
</span><span class="err">fastest:</span><span class="w"> </span><span class="mi">5</span><span class="p">,</span><span class="w">
</span><span class="err">pathname:</span><span class="w"> </span><span class="s2">"/"</span><span class="p">,</span><span class="w">
</span><span class="err">slowest:</span><span class="w"> </span><span class="mi">914</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre>
<p>Index (home) page in v1.10.0 </p>
<pre class="highlight"><code><span class="err">//</span><span class="w"> </span><span class="err">Ember</span><span class="w"> </span><span class="err">v</span><span class="mf">1.10</span><span class="err">.</span><span class="mi">0</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="err">average:</span><span class="w"> </span><span class="mf">244.7352818181818</span><span class="p">,</span><span class="w">
</span><span class="err">durations:</span><span class="w"> </span><span class="mi">220</span><span class="p">,</span><span class="w">
</span><span class="err">fastest:</span><span class="w"> </span><span class="mi">5</span><span class="p">,</span><span class="w">
</span><span class="err">pathname:</span><span class="w"> </span><span class="s2">"/"</span><span class="p">,</span><span class="w">
</span><span class="err">slowest:</span><span class="w"> </span><span class="mf">1854.635</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre>
<p>The most visited (post) page in v1.8.1</p>
<p>/metrics/durations?name=post_view&pathname=mongoose&emberVersion=1.8</p>
<pre class="highlight"><code><span class="err">//</span><span class="w"> </span><span class="err">Ember</span><span class="w"> </span><span class="err">v</span><span class="mf">1.8</span><span class="err">.</span><span class="mi">1</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="err">average:</span><span class="w"> </span><span class="mf">251.42101618705053</span><span class="p">,</span><span class="w">
</span><span class="err">durations:</span><span class="w"> </span><span class="mi">1668</span><span class="p">,</span><span class="w">
</span><span class="err">fastest:</span><span class="w"> </span><span class="mi">8</span><span class="p">,</span><span class="w">
</span><span class="err">pathname:</span><span class="w"> </span><span class="s2">"/posts/develop-a-restful-api-using-nodejs-with-express-and-mongoose"</span><span class="p">,</span><span class="w">
</span><span class="err">slowest:</span><span class="w"> </span><span class="mi">3696</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre>
<p>The most visited (post) page in v1.10.0</p>
<p>/metrics/durations?name=post_view&pathname=mongoose&emberVersion=1.10</p>
<pre class="highlight"><code><span class="err">//</span><span class="w"> </span><span class="err">Ember</span><span class="w"> </span><span class="err">v</span><span class="mf">1.10</span><span class="err">.</span><span class="mi">0</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="err">average:</span><span class="w"> </span><span class="mf">232.02967880485514</span><span class="p">,</span><span class="w">
</span><span class="err">durations:</span><span class="w"> </span><span class="mi">2142</span><span class="p">,</span><span class="w">
</span><span class="err">fastest:</span><span class="w"> </span><span class="mi">8</span><span class="p">,</span><span class="w">
</span><span class="err">pathname:</span><span class="w"> </span><span class="s2">"/posts/develop-a-restful-api-using-nodejs-with-express-and-mongoose"</span><span class="p">,</span><span class="w">
</span><span class="err">slowest:</span><span class="w"> </span><span class="mi">5726</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre>
<p>Long list (1,000 metrics) in v1.10.0, in Chrome</p>
<p>/api/metrics/durations?name=metrics_table&emberVersion=1.10&userAgent=Chrome</p>
<pre class="highlight"><code><span class="err">//</span><span class="w"> </span><span class="err">Ember</span><span class="w"> </span><span class="err">Version</span><span class="w"> </span><span class="mf">1.10</span><span class="err">.</span><span class="mi">0</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="err">average:</span><span class="w"> </span><span class="mf">690.5488399999999</span><span class="p">,</span><span class="w">
</span><span class="err">durations:</span><span class="w"> </span><span class="mi">25</span><span class="p">,</span><span class="w">
</span><span class="err">fastest:</span><span class="w"> </span><span class="mf">427.563</span><span class="p">,</span><span class="w">
</span><span class="err">pathname:</span><span class="w"> </span><span class="s2">"/metrics"</span><span class="p">,</span><span class="w">
</span><span class="err">slowest:</span><span class="w"> </span><span class="mf">1142.15</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre>
<p>Long list (1,000 metrics) in v1.8.1, in Chrome</p>
<p>/metrics/durations?name=metrics_table&emberVersion=1.8&userAgent=Chrome</p>
<pre class="highlight"><code><span class="err">//</span><span class="w"> </span><span class="err">Ember</span><span class="w"> </span><span class="err">Version</span><span class="w"> </span><span class="mf">1.8</span><span class="err">.</span><span class="mi">1</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="err">average:</span><span class="w"> </span><span class="mf">682.4195625</span><span class="p">,</span><span class="w">
</span><span class="err">durations:</span><span class="w"> </span><span class="mi">16</span><span class="p">,</span><span class="w">
</span><span class="err">fastest:</span><span class="w"> </span><span class="mf">457.012</span><span class="p">,</span><span class="w">
</span><span class="err">pathname:</span><span class="w"> </span><span class="s2">"/metrics"</span><span class="p">,</span><span class="w">
</span><span class="err">slowest:</span><span class="w"> </span><span class="mf">991.684</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre>
<h4>On Mobile</h4>
<p>Index (home) page in v1.10.0, on Mobile</p>
<p>/metrics/durations?name=index_view&emberVersion=1.10&userAgent=Mobile</p>
<pre class="highlight"><code><span class="err">//</span><span class="w"> </span><span class="err">Ember</span><span class="w"> </span><span class="err">Version</span><span class="w"> </span><span class="mf">1.10</span><span class="err">.</span><span class="mi">0</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="err">average:</span><span class="w"> </span><span class="mf">580.4696976744186</span><span class="p">,</span><span class="w">
</span><span class="err">durations:</span><span class="w"> </span><span class="mi">43</span><span class="p">,</span><span class="w">
</span><span class="err">fastest:</span><span class="w"> </span><span class="mi">160</span><span class="p">,</span><span class="w">
</span><span class="err">pathname:</span><span class="w"> </span><span class="s2">"/"</span><span class="p">,</span><span class="w">
</span><span class="err">slowest:</span><span class="w"> </span><span class="mi">1490</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre>
<p>Index (home) page in v1.8.1, on Mobile</p>
<p>/metrics/durations?name=index_view&emberVersion=1.8&userAgent=Mobile</p>
<pre class="highlight"><code><span class="err">//</span><span class="w"> </span><span class="err">Ember</span><span class="w"> </span><span class="err">Version</span><span class="w"> </span><span class="mf">1.8</span><span class="err">.</span><span class="mi">1</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="err">average:</span><span class="w"> </span><span class="mf">515.881875</span><span class="p">,</span><span class="w">
</span><span class="err">durations:</span><span class="w"> </span><span class="mi">8</span><span class="p">,</span><span class="w">
</span><span class="err">fastest:</span><span class="w"> </span><span class="mi">330</span><span class="p">,</span><span class="w">
</span><span class="err">pathname:</span><span class="w"> </span><span class="s2">"/"</span><span class="p">,</span><span class="w">
</span><span class="err">slowest:</span><span class="w"> </span><span class="mi">914</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre>
<p>Long list (1,000 metrics) in v1.8.1, on Mobile</p>
<p>/metrics/durations?name=metrics_table&emberVersion=1.8&userAgent=Mobile</p>
<pre class="highlight"><code><span class="err">//</span><span class="w"> </span><span class="err">Ember</span><span class="w"> </span><span class="err">Version</span><span class="w"> </span><span class="mf">1.8</span><span class="err">.</span><span class="mi">1</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="err">average:</span><span class="w"> </span><span class="mf">2179.4</span><span class="p">,</span><span class="w">
</span><span class="err">durations:</span><span class="w"> </span><span class="mi">10</span><span class="p">,</span><span class="w">
</span><span class="err">fastest:</span><span class="w"> </span><span class="mi">871</span><span class="p">,</span><span class="w">
</span><span class="err">pathname:</span><span class="w"> </span><span class="s2">"/metrics"</span><span class="p">,</span><span class="w">
</span><span class="err">slowest:</span><span class="w"> </span><span class="mi">5380</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre>
<p>Long list (1,000 metrics) in v1.10.0, on Mobile</p>
<p>/metrics/durations?name=metrics_table&emberVersion=1.10&userAgent=Mobile</p>
<pre class="highlight"><code><span class="err">//</span><span class="w"> </span><span class="err">Ember</span><span class="w"> </span><span class="err">Version</span><span class="w"> </span><span class="mf">1.10</span><span class="err">.</span><span class="mi">0</span><span class="w">
</span><span class="p">{</span><span class="w">
</span><span class="err">average:</span><span class="w"> </span><span class="mf">1617.4285714285713</span><span class="p">,</span><span class="w">
</span><span class="err">durations:</span><span class="w"> </span><span class="mi">7</span><span class="p">,</span><span class="w">
</span><span class="err">fastest:</span><span class="w"> </span><span class="mi">1014</span><span class="p">,</span><span class="w">
</span><span class="err">pathname:</span><span class="w"> </span><span class="s2">"/metrics"</span><span class="p">,</span><span class="w">
</span><span class="err">slowest:</span><span class="w"> </span><span class="mi">2285</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre>
<h2>What I learned about my Performance in my Ember app</h2>
<p>I have not read "How not to lie with statistics, The correct way to summarize benchmark results". So, the measurements I've taken at this point, are only an exploration of a data set that is not normalized.</p>
<p>From jayconrod.com… "The geometric mean is intended for averaging ratios (normalized data)" … "Don't bother summarizing raw data without normalizing it."</p>
<p>So, I can't make any conclusions at this point, I can only ask more questions. For example, how do I normalize the data I've that I've captured and calculate the geometric means?</p>
<p><strong>UPDATE: 3/8/15</strong> I've calculated my findings in a new post: <a href="proxy.php?url=/posts/how-much-faster-is-htmlbars-than-handlebars" target="_blank" rel="noopener noreferrer">How Much Faster is HTMLBars Than Handlebars?</a></p>
<p><em>Regarding the upgrade from Handlebars to HTMLbars…</em></p>
<ul>
<li>Looking at the average speed of rendering the home page did not seem to show a performance gain, but the data set is not normalized</li>
<li>The same can be said for the speed of rendering the templates on my post pages</li>
<li>Though I don't believe that rendering a long list doesn't matter to my users, after experimenting with a long list I did notice notice any big gains in performance looking at the fastest rendering times in the data I collected</li>
</ul>
<p>However what appears to be good news, is that the fastest speeds did improve, and even more so on mobile.</p>
<p><em>On Mobile…</em></p>
<p>It appears that…</p>
<ul>
<li>I may expect a 25% - 50% gain in performance for the fastest time to render.
<em>The performance gains my users will benefit from (after the upgrade) are limited to the users who have newer mobile devices</em></li>
</ul>
<p>Thanks to the hard work of the Ember.js contributors and core team its my expectation that users browsing my site should see a boost in performance. </p>
<p>Since I found these measurements valuable, I'll continue to collect them and review as I try out various beta versions of Ember.js. Recently another release of the 1.11.0 series was released, so I'll give it a try soon.</p>
<p>Once I discover how to normalize the metrics and calculate a proper geometric mean then I can draw some more definitive summaries about the measurements.</p>
<p>At this point, I am very satisfied to have these metrics available from a production build, on a production server from a wide set of users, I get around 10,000 unique users and 30,000 pageviews per month on my blog app. This is not huge, but does provide some good insight into trends I can expect from previous, current and future releases of Ember.</p>
<p>I gave a talk on this topic, here are <a href="proxy.php?url=https://gist.github.com/pixelhandler/f01a1703721d31de0de1" target="_blank" rel="noopener noreferrer">my presentation notes</a></p>
Develop Ember.Components for Sharing as Ember CLI Addons, A Practical Examplehttps://pixelhandler.dev/posts/develop-embercomponents-for-sharing-as-ember-cli-addons-a-practical-example2014-11-25T00:00:00+00:002014-11-25T00:00:00+00:00Billy HeatonI have a simple micro library of components that I wanted to convert to an [Ember CLI] Addon. The [Ember Off Canvas Components] repository contains a group of Ember.js Components that interact to c...<h2>Converting a Micro Library of Components to an Ember CLI Addon</h2>
<p>I have a simple micro library of components that I wanted to convert to an <a href="proxy.php?url=http://www.ember-cli.com" target="_blank" rel="noopener noreferrer">Ember CLI</a> Addon. The <a href="proxy.php?url=https://github.com/pixelhandler/ember-off-canvas-components" target="_blank" rel="noopener noreferrer">Ember Off Canvas Components</a> repository contains a group of Ember.js Components that interact to create an <a href="proxy.php?url=http://jasonweaver.name/lab/offcanvas/" target="_blank" rel="noopener noreferrer">Off Canvas</a> layout for use in a web application (powered by Ember). Here is an example of that interface included in another library: <a href="proxy.php?url=http://getuikit.com/docs/offcanvas.html" target="_blank" rel="noopener noreferrer">UIkit Off-canvas</a>. I'm betting on the idea that most developers would rather pick a component (for the needs in the application they are creating) versus pick an entire UI library that has some common functionality.</p>
<p>I was inspired by the <a href="proxy.php?url=https://github.com/ef4/liquid-fire" target="_blank" rel="noopener noreferrer">Liquid Fire</a> packaging which includes standalone files with each release for developers who are not yet using Ember CLI. The <a href="proxy.php?url=http://www.ember-cli.com/#developing-addons-and-blueprints" target="_blank" rel="noopener noreferrer">developing addons</a> documentation for Ember CLI outlines the process of creating an Addon. I found that the <a href="proxy.php?url=https://github.com/ef4/liquid-fire/tree/master/packaging" target="_blank" rel="noopener noreferrer">packaging</a> scripts in the Liquid Fire repository were straight forward and a good template for including a secondary build of a standalone release.</p>
<p>Prior to converting the off canvas components library to an addon I used <a href="proxy.php?url=https://github.com/realityendshere/emberella-component-blueprint" target="_blank" rel="noopener noreferrer">Emberella Component Blueprint</a> which provided great tooling for component development. Most of the successful platforms or content management systems that I've used over the past ten years has some type of a marketplace, bundle or packing solution for extending a framework. The <a href="proxy.php?url=http://www.emberaddons.com" target="_blank" rel="noopener noreferrer">Ember Addons</a> website aggregates and provides a searchable collection of ember addons (NPM packages tagged with the ember-addon keyword). Which is a desirable way to share and find extensions for developing applications with Ember.js.</p>
<p>I was on the fence for some time about using Ember CLI addons for components, at first it seemed that addons should be for tooling only; specific to extending the functionity (tooling) that Ember CLI provides. However, after some consideration and after using the standalone release of Liquid Fire it seemed to me that the time was right to give components (as addons) a chance. I've used Ember CLI for some time and now use it during my day job too. I've found that using a common set of development tools is desireable. I like using Testem, Broccoli, Ember QUnit helpers; for me, (my personal) developer happiness increases when I can standardize the way I develop for both applications and libraries. I expect that it would also be a nice-to-have for developers who may eventually work on code I've shipped, perhaps someone like Elad <a href="proxy.php?url=https://twitter.com/Elad/status/537037555154554880" target="_blank" rel="noopener noreferrer">https://twitter.com/Elad/status/537037555154554880</a>.</p>
<h2>A Practical Example</h2>
<p>So here is a practical guide on how to develop an addon for a component or group of components that work together...</p>
<p>The <a href="proxy.php?url=https://github.com/emberjs/rfcs/pull/15" target="_blank" rel="noopener noreferrer">Ember 2.0 RFC</a> mentions "...communication between components is often most naturally expressed as events or callbacks." which resonates with me. The off-canvas components addon facilitates communication between components using browser events (and bubbling). I did not use any bindings between components or a special dependency as an evented mediator between the components. The browser already provide events, so the components simply talk via custom events which need to be registered with <a href="proxy.php?url=http://emberjs.com/api/classes/Ember.Application.html#property_customEvents" target="_blank" rel="noopener noreferrer">Ember.Application.customEvents</a></p>
<p>Since I've already shipped the off-canvas components library and it's used in a production application, I thought that it would be a valuable exercise to convert from a custom build to a more common build using Ember CLI. So, I started with following the docs for <a href="proxy.php?url=http://www.ember-cli.com/#developing-addons-and-blueprints" target="_blank" rel="noopener noreferrer">developing addons</a>. But, I quickly found myself scratching my head. As I followed the guide, questions like "How do I..." and "Why do I do this..." began to interupt me. I hope that this replay of the process I found successful (in buiding an addon of components) will help others find some clartity.</p>
<p>If you are interested, perhaps peruse my <a href="proxy.php?url=https://github.com/pixelhandler/ember-off-canvas-components/commits/master" target="_blank" rel="noopener noreferrer">commit history</a> while converting over. from commit/0df9223 on 11/19/14 thru 63b84d8 on 11/22/14.</p>
<p>As I followed the docs and attempted to create an addon, I took note of a few topics that interested me or caused some curiosity.</p>
<p>I took the approach of using the <code>addon</code> folder for the components source code and importing the prototypes in the <code>app</code> directory. This seemed like the best way to provide a component that can be used as-is; and can be easily extended or changed at-will by the consuming application developer who may need to extend or re-open the prototype in their own application source code.</p>
<h2>Start by Generating the Files you Need</h2>
<p>The docs suggest using the <code>ember</code> command to generate an addon then to generate the building blocks you will include in your addon.</p>
<p><code>ember addon ember-off-canvas-components</code> is the command I used to kick off the scaffold of my converstion to an addon.</p>
<p>Next, use the <code>ember generate component</code> command to create the basic files you need for a component, including unit test files.</p>
<p>You can update the test with any test code you've already written or perhaps take the opportunity to mint a fresh new test.</p>
<p>To begin, a super simple test should fail, if you add an assertion for using <a href="proxy.php?url=http://w3c.github.io/webcomponents/spec/custom/" target="_blank" rel="noopener noreferrer">Custom Elements</a>. (<a href="proxy.php?url=http://www.w3.org/TR/components-intro/" target="_blank" rel="noopener noreferrer">Web Components</a> use <a href="proxy.php?url=http://w3c.github.io/webcomponents/spec/custom/" target="_blank" rel="noopener noreferrer">Custom Elements</a>, right?)</p>
<pre class="highlight"><code><span class="k">import</span> <span class="p">{</span> <span class="nx">moduleForComponent</span><span class="p">,</span> <span class="nx">test</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">ember-qunit</span><span class="dl">'</span><span class="p">;</span>
<span class="nf">moduleForComponent</span><span class="p">(</span><span class="dl">'</span><span class="s1">on-canvas</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">OnCanvasComponent</span><span class="dl">'</span><span class="p">);</span>
<span class="nf">test</span><span class="p">(</span><span class="dl">'</span><span class="s1">it renders element with tagName on-canvas</span><span class="dl">'</span><span class="p">,</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
<span class="nf">expect</span><span class="p">(</span><span class="mi">3</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">component</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nf">subject</span><span class="p">();</span>
<span class="nf">equal</span><span class="p">(</span><span class="nx">component</span><span class="p">.</span><span class="nx">_state</span><span class="p">,</span> <span class="dl">'</span><span class="s1">preRender</span><span class="dl">'</span><span class="p">);</span>
<span class="k">this</span><span class="p">.</span><span class="nf">append</span><span class="p">();</span>
<span class="nf">equal</span><span class="p">(</span><span class="nx">component</span><span class="p">.</span><span class="nx">_state</span><span class="p">,</span> <span class="dl">'</span><span class="s1">inDOM</span><span class="dl">'</span><span class="p">);</span>
<span class="nf">equal</span><span class="p">(</span><span class="nx">component</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="dl">'</span><span class="s1">element</span><span class="dl">'</span><span class="p">).</span><span class="nx">tagName</span><span class="p">,</span> <span class="dl">'</span><span class="s1">on-canvas</span><span class="dl">'</span><span class="p">.</span><span class="nf">toUpperCase</span><span class="p">(),</span> <span class="dl">'</span><span class="s1">matches `on-canvas`</span><span class="dl">'</span><span class="p">);</span>
<span class="p">});</span>
</code></pre>
<p>Then I copied over the code for this component (this is the simplest one, all I needed was to use a custom element). After the <code>ember generate component on-canvas</code> command I copied the generated code into the <code>addon/components</code> directory.</p>
<pre class="highlight"><code><span class="k">import</span> <span class="nx">Ember</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">ember</span><span class="dl">'</span><span class="p">;</span>
<span class="cm">/**
To use this component in your app, add this to a template:
```handlebars
{{#on-canvas}}
{{#off-canvas-opener}}
<i class="fa fa-bars"></i>
{{/off-canvas-opener}}
<div class="on-canvas-body">
On Canvas Contents
</div>
{{/on-canvas}}
</span></code></pre>
<p>@extends Ember.Component<br>
*/</p>
<p>export default Ember.Component.extend({<br>
/**<br>
The type of element to render this view into. By default, samples will appear<br>
as <code><on-canvas/></code> elements.</p>
<pre class="highlight"><code>@property tagName
@type String
</code></pre>
<p>*/<br>
tagName: 'on-canvas',</p>
<p>classNames: ['on-canvas-default']<br>
});<br>
```</p>
<p>Next, I changed the module that was generated, <code>app/components/on-canvas.js</code>, to only import the prototype from the addon directory.</p>
<pre class="highlight"><code><span class="k">import</span> <span class="nx">OnCanvasComponent</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">ember-off-canvas-components/components/on-canvas</span><span class="dl">'</span><span class="p">;</span>
<span class="k">export</span> <span class="k">default</span> <span class="nx">OnCanvasComponent</span><span class="p">;</span>
</code></pre>
<h2>What About Initializers?</h2>
<p>I mentioned that the components talk using custom events, well the Ember Application will need to register the <code>customEvents</code>.</p>
<p>For example on click the component may toggle the display of the off canvas panel like so:</p>
<pre class="highlight"><code><span class="nx">click</span><span class="p">:</span> <span class="nf">function </span><span class="p">(</span><span class="nx">evt</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">eventName</span><span class="p">;</span>
<span class="k">if </span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="dl">'</span><span class="s1">useToggle</span><span class="dl">'</span><span class="p">))</span> <span class="p">{</span>
<span class="nx">eventName</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">toggleOffCanvas</span><span class="dl">'</span><span class="p">;</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="nx">eventName</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">expandOffCanvas</span><span class="dl">'</span><span class="p">;</span>
<span class="p">}</span>
<span class="nx">Ember</span><span class="p">.</span><span class="nf">$</span><span class="p">(</span><span class="nx">evt</span><span class="p">.</span><span class="nx">target</span><span class="p">).</span><span class="nf">trigger</span><span class="p">(</span><span class="nx">eventName</span><span class="p">);</span>
<span class="k">return</span> <span class="kc">false</span><span class="p">;</span>
<span class="p">}</span>
</code></pre>
<p>This requires an initializer to register the custom events I used in the group of components: <code>toggleOffCanvas</code>, <code>expandOffCanvas</code>, and <code>collapseOffCanvas</code>. </p>
<p>Again, using the command <code>ember generate initializer custom-events</code> creates the file I need in the <code>app/initializers</code> directory. I did the same thing I did with the component, I copied the generated file to <code>addon/initializers/custom-events.js</code> then pasted and updated the initializer code.</p>
<pre class="highlight"><code><span class="k">import</span> <span class="nx">Ember</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">ember</span><span class="dl">'</span><span class="p">;</span>
<span class="k">export</span> <span class="kd">function</span> <span class="nf">initialize</span><span class="p">(</span><span class="nx">container</span><span class="p">,</span> <span class="nx">application</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">customEvents</span> <span class="o">=</span> <span class="nx">application</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="dl">'</span><span class="s1">customEvents</span><span class="dl">'</span><span class="p">)</span> <span class="o">||</span> <span class="p">{};</span>
<span class="nx">Ember</span><span class="p">.</span><span class="nb">String</span><span class="p">.</span><span class="nf">w</span><span class="p">(</span><span class="dl">'</span><span class="s1">toggle expand collapse</span><span class="dl">'</span><span class="p">).</span><span class="nf">forEach</span><span class="p">(</span><span class="nf">function </span><span class="p">(</span><span class="nx">prefix</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">name</span> <span class="o">=</span> <span class="nx">Ember</span><span class="p">.</span><span class="nb">String</span><span class="p">.</span><span class="nf">fmt</span><span class="p">(</span><span class="dl">"</span><span class="s2">%@OffCanvas</span><span class="dl">"</span><span class="p">,</span> <span class="nx">prefix</span><span class="p">);</span>
<span class="nx">customEvents</span><span class="p">[</span><span class="nx">name</span><span class="p">]</span> <span class="o">=</span> <span class="nx">name</span><span class="p">;</span>
<span class="p">});</span>
<span class="nx">application</span><span class="p">.</span><span class="nf">set</span><span class="p">(</span><span class="dl">'</span><span class="s1">customEvents</span><span class="dl">'</span><span class="p">,</span> <span class="nx">customEvents</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">export</span> <span class="k">default</span> <span class="p">{</span>
<span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">ember-off-canvas-components/custom-events</span><span class="dl">'</span><span class="p">,</span>
<span class="na">initialize</span><span class="p">:</span> <span class="nx">Ember</span><span class="p">.</span><span class="nx">K</span>
<span class="p">};</span>
</code></pre>
<p>The initializer exports the <code>initialize</code> method which I import in the application. This will be come handy as I will need to import this initialzer in the app directory for apps consuming the addon as well as utilize the initializer in the packaging scripts for the standalone release to accompany each release of the addon.</p>
<p>In <code>app/initializers/eoc-custom-events.js</code> I added:</p>
<pre class="highlight"><code><span class="k">import</span> <span class="p">{</span> <span class="nx">initialize</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">ember-off-canvas-components/initializers/custom-events</span><span class="dl">'</span><span class="p">;</span>
<span class="k">export</span> <span class="k">default</span> <span class="p">{</span>
<span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">eoc-custom-events</span><span class="dl">'</span><span class="p">,</span>
<span class="na">initialize</span><span class="p">:</span> <span class="nx">initialize</span>
<span class="p">};</span>
</code></pre>
<p>I used a different name for the initializer, as I had some errors in the browser console about the name of the initializer 'custom-events' already being registered. So, I didn't fight it, just added a prefix.</p>
<p>Initializers can be tested too; the ember generator already setup a unit test file. In <code>tests/unit/initializers/custom-events-test.js</code>:</p>
<pre class="highlight"><code><span class="k">import</span> <span class="nx">Ember</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">ember</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">initialize</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">ember-off-canvas-components/initializers/custom-events</span><span class="dl">'</span><span class="p">;</span>
<span class="kd">var</span> <span class="nx">container</span><span class="p">,</span> <span class="nx">application</span><span class="p">;</span>
<span class="nf">module</span><span class="p">(</span><span class="dl">'</span><span class="s1">CustomEventsInitializer</span><span class="dl">'</span><span class="p">,</span> <span class="p">{</span>
<span class="na">setup</span><span class="p">:</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
<span class="nx">Ember</span><span class="p">.</span><span class="nf">run</span><span class="p">(</span><span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
<span class="nx">container</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Ember</span><span class="p">.</span><span class="nc">Container</span><span class="p">();</span>
<span class="nx">application</span> <span class="o">=</span> <span class="nx">Ember</span><span class="p">.</span><span class="nx">Application</span><span class="p">.</span><span class="nf">create</span><span class="p">();</span>
<span class="nx">application</span><span class="p">.</span><span class="nf">deferReadiness</span><span class="p">();</span>
<span class="p">});</span>
<span class="p">}</span>
<span class="p">});</span>
<span class="nf">test</span><span class="p">(</span><span class="dl">'</span><span class="s1">it sets customEvents on the application</span><span class="dl">'</span><span class="p">,</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
<span class="nf">expect</span><span class="p">(</span><span class="mi">1</span><span class="p">);</span>
<span class="nf">initialize</span><span class="p">(</span><span class="nx">container</span><span class="p">,</span> <span class="nx">application</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">expected</span> <span class="o">=</span> <span class="p">{</span>
<span class="na">toggleOffCanvas</span><span class="p">:</span> <span class="dl">"</span><span class="s2">toggleOffCanvas</span><span class="dl">"</span><span class="p">,</span>
<span class="na">expandOffCanvas</span><span class="p">:</span> <span class="dl">"</span><span class="s2">expandOffCanvas</span><span class="dl">"</span><span class="p">,</span>
<span class="na">collapseOffCanvas</span><span class="p">:</span> <span class="dl">"</span><span class="s2">collapseOffCanvas</span><span class="dl">"</span>
<span class="p">};</span>
<span class="nf">deepEqual</span><span class="p">(</span><span class="nx">application</span><span class="p">.</span><span class="nx">customEvents</span><span class="p">,</span> <span class="nx">expected</span><span class="p">);</span>
<span class="p">});</span>
</code></pre>
<h2>What About an Example App for gh-pages?</h2>
<p>Ember CLI includes a <code>dummy</code> app that you can work with during development with the <code>ember server</code> command or use as a demo app with the <code>ember build</code> script which builds the dummy app in your <code>/dist</code> directory. Here is the <a href="proxy.php?url=http://pixelhandler.github.io/ember-off-canvas-components/" target="_blank" rel="noopener noreferrer">demo</a> I created with the dummy app.</p>
<p>In <code>tests/dummy</code> there are folders for <code>app</code>, <code>config</code>, and <code>public</code>, the makings of an ember application. In the <a href="proxy.php?url=https://github.com/pixelhandler/ember-off-canvas-components/tree/master/tests/dummy" target="_blank" rel="noopener noreferrer">dummy directory</a> I added a few files I had already used in the previous iteration of this library (the pre-ember-cli version) as an example app. So I created the needed index controller and route, as well as templates for the application, index and a partial off-canvas template. I copied a few styles to the dummy app too. I needed to update the <code>tests/dummy/config/environment.js</code> to add a custom <code>contentSecurityPolicy</code> with settings to squelch some noisy errors in the browser console since I used font-awesome, <code>'style-src'</code> needed <code>'unsafe-inline'</code> and a cdn link. Perhaps research <code>contentSecurityPolicy</code> to setup your demo (dummy application) to your liking.</p>
<p>Like developing a application with ember-cli, the <code>ember server</code> command rebuilds the source of the dummy app as you edit the code.</p>
<p>I used the <code>ember build</code> command then copy the output from the <code>/dist</code> directory to a separate <code>gh-pages</code> branch to share a demo app with the repository on Github.</p>
<h2>So you want to use SASS, me too</h2>
<p>I had to consider a few concerns and curiosities in order to include node-sass as a dev dependency. After some trial and error, I found...</p>
<ol>
<li>Brocfile doesn’t import vendor files, index.js does</li>
<li>I need a custom script for compiling CSS with node-sass and execute the compile step in the Brocfile</li>
<li>No need to include the generated CSS in the repo - .gitignore the compiled CSS file(s)</li>
</ol>
<p>I ended up writing a script to compile the SASS to CSS and write the style sheet for the custom elements in the <code>/vendor</code> directory.</p>
<ul>
<li>For SASS I used a directory <code>addon/styles/scss</code> for the .scss files, and setup <code>config/environment.js</code> with paths for compiling/output.</li>
</ul>
<p>The compile-css.js script:</p>
<pre class="highlight"><code><span class="cm">/* global require, module */</span>
<span class="kd">var</span> <span class="nx">sass</span> <span class="o">=</span> <span class="nf">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">node-sass</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">path</span> <span class="o">=</span> <span class="nf">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">path</span><span class="dl">'</span><span class="p">);</span>
<span class="nx">module</span><span class="p">.</span><span class="nx">exports</span> <span class="o">=</span> <span class="nf">function </span><span class="p">(</span><span class="nx">env</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">env</span> <span class="o">=</span> <span class="nx">env</span> <span class="o">||</span> <span class="dl">'</span><span class="s1">development</span><span class="dl">'</span><span class="p">;</span>
<span class="kd">var</span> <span class="nx">configPath</span> <span class="o">=</span> <span class="nx">path</span><span class="p">.</span><span class="nf">resolve</span><span class="p">(</span><span class="nx">__dirname</span><span class="p">,</span> <span class="dl">'</span><span class="s1">config/environment</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">config</span> <span class="o">=</span> <span class="nf">require</span><span class="p">(</span><span class="nx">configPath</span><span class="p">)(</span><span class="nx">env</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">cssFile</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">vendor/</span><span class="dl">'</span> <span class="o">+</span> <span class="nx">config</span><span class="p">.</span><span class="nx">addonPrefix</span> <span class="o">+</span> <span class="dl">'</span><span class="s1">.css</span><span class="dl">'</span><span class="p">;</span>
<span class="kd">var</span> <span class="nx">vendorFile</span> <span class="o">=</span> <span class="nx">path</span><span class="p">.</span><span class="nf">resolve</span><span class="p">(</span><span class="nx">__dirname</span><span class="p">,</span> <span class="nx">cssFile</span><span class="p">);</span>
<span class="nx">sass</span><span class="p">.</span><span class="nf">renderFile</span><span class="p">({</span>
<span class="na">file</span><span class="p">:</span> <span class="nx">path</span><span class="p">.</span><span class="nf">resolve</span><span class="p">(</span><span class="nx">__dirname</span><span class="p">,</span> <span class="nx">config</span><span class="p">.</span><span class="nx">sassMain</span><span class="p">),</span>
<span class="na">success</span><span class="p">:</span> <span class="kd">function</span><span class="p">(</span><span class="cm">/*css*/</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">node-sass compiled</span><span class="dl">'</span><span class="p">,</span> <span class="nx">vendorFile</span><span class="p">.</span><span class="nf">split</span><span class="p">(</span><span class="nx">__dirname</span><span class="p">)[</span><span class="mi">1</span><span class="p">]);</span>
<span class="p">},</span>
<span class="na">error</span><span class="p">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">error</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nf">error</span><span class="p">(</span><span class="nx">error</span><span class="p">);</span>
<span class="p">},</span>
<span class="na">includePaths</span><span class="p">:</span> <span class="p">[</span> <span class="nx">path</span><span class="p">.</span><span class="nf">resolve</span><span class="p">(</span><span class="nx">__dirname</span><span class="p">,</span> <span class="nx">config</span><span class="p">.</span><span class="nx">sassIncludePath</span><span class="p">)</span> <span class="p">],</span>
<span class="na">outputStyle</span><span class="p">:</span> <span class="p">(</span><span class="nx">env</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">development</span><span class="dl">'</span><span class="p">)</span> <span class="p">?</span> <span class="dl">'</span><span class="s1">nested</span><span class="dl">'</span> <span class="p">:</span> <span class="dl">'</span><span class="s1">compressed</span><span class="dl">'</span><span class="p">,</span>
<span class="na">outFile</span><span class="p">:</span> <span class="nx">vendorFile</span><span class="p">,</span>
<span class="na">precision</span><span class="p">:</span> <span class="mi">5</span><span class="p">,</span>
<span class="na">sourceMap</span><span class="p">:</span> <span class="p">(</span><span class="nx">env</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">development</span><span class="dl">'</span><span class="p">)</span>
<span class="p">});</span>
<span class="p">};</span>
</code></pre>
<p>I added a <code>sass</code> script to the package.json file, which uses the above compile script, <code>"sass": "node -e \"require('./compile-css.js')()\""</code>.</p>
<p>The compile script is called from the Brocfile I expect that the css will be compiled on each change. </p>
<pre class="highlight"><code><span class="cm">/* global require, module */</span>
<span class="kd">var</span> <span class="nx">EmberAddon</span> <span class="o">=</span> <span class="nf">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">ember-cli/lib/broccoli/ember-addon</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">app</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">EmberAddon</span><span class="p">();</span>
<span class="nf">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">./compile-css</span><span class="dl">'</span><span class="p">)(</span><span class="nx">app</span><span class="p">.</span><span class="nx">env</span><span class="p">);</span>
<span class="nx">module</span><span class="p">.</span><span class="nx">exports</span> <span class="o">=</span> <span class="nx">app</span><span class="p">.</span><span class="nf">toTree</span><span class="p">();</span>
</code></pre>
<p>Since the repository now has a dependency to compile SASS to CSS I added a <code>postinstall</code> script in the package.json using a shell script <code>postinstall.sh</code>:</p>
<pre class="highlight"><code><span class="c">#!/bin/sh</span>
<span class="nb">echo</span> <span class="s2">"postinstall..."</span>
<span class="nb">echo</span> <span class="s2">"installing bower dependendies"</span>
npm <span class="nb">install </span>bower
./node_modules/.bin/bower <span class="nb">install
echo</span> <span class="s2">"installing node-sass"</span>
npm <span class="nb">install </span>node-sass
<span class="nb">echo</span> <span class="s2">"compiling CSS to /vendor with node-sass"</span>
./node_modules/.bin/node-sass ./addon/styles/scss/main.scss ./vendor/ember-off-canvas-components.css
</code></pre>
<h2>Ok, Ship It!</h2>
<p>If your only targeted users are ember-cli users then just edit the package.json file, perhaps add a .npmignore file and surprise the travis config just works out of the box.</p>
<p>So are you telling me that I can develop <a href="proxy.php?url=http://www.w3.org/TR/components-intro/" target="_blank" rel="noopener noreferrer">Web Components</a> and ship them with Ember CLI. No and yes, wait. I plan to do a followup post on using Native Web Components with an Ember component utilizing the strengths of Ember bindings. That's another topic for <a href="proxy.php?url=http://w3c.github.io/webcomponents/spec/custom/" target="_blank" rel="noopener noreferrer">Custom Elements</a>, <a href="proxy.php?url=http://w3c.github.io/webcomponents/spec/imports/" target="_blank" rel="noopener noreferrer">HTML Imports</a>, <a href="proxy.php?url=https://html.spec.whatwg.org/multipage/scripting.html#the-template-element" target="_blank" rel="noopener noreferrer">Template</a> and <a href="proxy.php?url=http://w3c.github.io/webcomponents/spec/shadow/" target="_blank" rel="noopener noreferrer">Shadow DOM</a>. Ember Components are currently isolated views, "An <code>Ember.Component</code> is a view that is completely isolated... There is no access to the surrounding context or outer controller; all contextual information must be passed in". But remember the Ember 2.0 rfc topic is filled with plans for components. That said developing component will become a significant area of focus for developers working with Ember.</p>
<p>That said, the genterated package.json file is already tagged with the <code>ember-addon</code> keyword. So you can login and publish at will.</p>
<ol>
<li><code>npm login</code> follow the prompts, of course you will need a <a href="proxy.php?url=https://www.npmjs.org" target="_blank" rel="noopener noreferrer">npmjs.org</a> account</li>
<li><code>npm publish .</code> from inside the root directory of your addon</li>
</ol>
<h3>Build, Test and Release</h3>
<p>I wrote a script <code>prepublish.sh</code> to use in the package.json scripts do the <code>prepublish</code> tasks of cleaning, building and executing tests.</p>
<pre class="highlight"><code><span class="c">#!/bin/sh</span>
<span class="nb">echo</span> <span class="s2">"clean vendor directory..."</span>
<span class="nb">rm</span> <span class="nt">-fr</span> ./vendor/<span class="k">*</span>
<span class="nb">echo</span> <span class="s2">"build and test..."</span>
ember build
ember <span class="nb">test
echo</span> <span class="s2">"building global/shim dist files..."</span>
<span class="nb">rm</span> <span class="nt">-fr</span> ./dist
<span class="nb">cd</span> ./packaging
../node_modules/.bin/broccoli build ../dist
</code></pre>
<p>After considering what will be published and trying out the prepublish script I made a few more tweeks:</p>
<ul>
<li>Packaging builds standalone library files (.js and .css) to the main <code>/dist</code> directory. See the Standalone section for more details on packaging.</li>
<li><code>config/envrionment.js</code> file needs development and test envrionment definitions</li>
<li>Add a <code>.npmignore</code> file to ignore the client dependency files for the NPM release.</li>
<li>in package.json set the main file to index.js</li>
</ul>
<p>Before I actually published the a release on <a href="proxy.php?url=https://www.npmjs.org" target="_blank" rel="noopener noreferrer">npmjs.org</a> I wanted to provide a standalone release for developers not yet using Ember CLI. You can too, read on...</p>
<p>I added a <code>clean</code> script to the package.json and use the clean and prepublish scripts to npm publish a version (npm login req) so prior to <code>npm login</code> and <code>npm publish .</code> I run <code>npm run clean</code>. The <code>prepublish</code> script will be run with the publish command.</p>
<h3>Can any Ember Developer Use my Addon?</h3>
<p>If you take a few steps to learn <a href="proxy.php?url=https://github.com/broccolijs/broccoli" target="_blank" rel="noopener noreferrer">Broccoli</a> to package up a standalone release. Hey someone has already done the heavy lifting. I'm hoping that the <a href="proxy.php?url=https://github.com/ef4/liquid-fire/tree/master/packaging" target="_blank" rel="noopener noreferrer">Liquid Fire packaging</a> scripts will themselves become an addon.</p>
<h2>A Standalone Release</h2>
<p>With the Ember Off Canvas Components addon/library I included a copy of the Liquid Fire packaing scripts with a few customizations and generalization changes. See the <a href="proxy.php?url=https://github.com/pixelhandler/ember-off-canvas-components/tree/master/packaging" target="_blank" rel="noopener noreferrer">ember-off-canvas-components packaging</a> and <a href="proxy.php?url=https://github.com/pixelhandler/ember-off-canvas-components/blob/master/packaging/README-packaging.md" target="_blank" rel="noopener noreferrer">packaging README</a>.</p>
<p>The Brocfile.js, registry.js, and wrap.js have a couple generalization changes to use the <a href="proxy.php?url=https://github.com/pixelhandler/ember-off-canvas-components/blob/master/config/environment.js" target="_blank" rel="noopener noreferrer">addon config</a></p>
<p>Then there is the custom needs for shipping a library aside from Ember CLI. The standalone release of the addon will need to register the components in the Application continer so you can use the fancy helpers in the templates, e.g. <code>{{#off-canvas}}{{/off-canvas}}</code> (once HTMLBars lands that will look much more like a component). Also the standalone release will need to run any initializers you provide in your addon.</p>
<h3>Exporting to a Global</h3>
<p>Create an <code>index.js</code> in the addon folder for exporting the library attached to a global variable with a standalone build, like so:</p>
<pre class="highlight"><code><span class="k">import</span> <span class="nx">EOCViewportComponent</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./components/eoc-viewport</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="nx">OnCanvasComponent</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./components/on-canvas</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="nx">OffCanvasComponent</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./components/off-canvas</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="nx">OffCanvasOpenerComponent</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./components/off-canvas-opener</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="nx">OffCanvasCloserComponent</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">./components/off-canvas-closer</span><span class="dl">'</span><span class="p">;</span>
<span class="k">export</span> <span class="p">{</span>
<span class="nx">EOCViewportComponent</span><span class="p">,</span>
<span class="nx">OnCanvasComponent</span><span class="p">,</span>
<span class="nx">OffCanvasComponent</span><span class="p">,</span>
<span class="nx">OffCanvasOpenerComponent</span><span class="p">,</span>
<span class="nx">OffCanvasCloserComponent</span>
<span class="p">};</span>
</code></pre>
<p>This above export is not needed by an addon, but I guess you could use it as an entry point if you choose to.</p>
<h3>The Glue</h3>
<p>The packaging scripts utilize a glue.js file to meet the needs of shipping a standalone release.</p>
<p>The following is used to ship the library code and provide access on a global variable, <code>window.EmberOffCanvasComponents</code> it uses the index.js file in the root of the addon directory to import assign. It also uses a shim to add the container references. Finally, I wrapped the addon's initializer to register custom events using a <code>ember-off-canvas-components-shim</code> initializer.</p>
<pre class="highlight"><code><span class="cm">/* global define, require, window */</span>
<span class="kd">var</span> <span class="nx">addonName</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">ember-off-canvas-components</span><span class="dl">'</span><span class="p">;</span>
<span class="nf">define</span><span class="p">(</span><span class="dl">'</span><span class="s1">ember</span><span class="dl">'</span><span class="p">,</span> <span class="p">[</span><span class="dl">"</span><span class="s2">exports</span><span class="dl">"</span><span class="p">],</span> <span class="kd">function</span><span class="p">(</span><span class="nx">__exports__</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">__exports__</span><span class="p">[</span><span class="dl">'</span><span class="s1">default</span><span class="dl">'</span><span class="p">]</span> <span class="o">=</span> <span class="nb">window</span><span class="p">.</span><span class="nx">Ember</span><span class="p">;</span>
<span class="p">});</span>
<span class="kd">var</span> <span class="nx">index</span> <span class="o">=</span> <span class="nx">addonName</span> <span class="o">+</span> <span class="dl">'</span><span class="s1">/index</span><span class="dl">'</span><span class="p">;</span>
<span class="nf">define</span><span class="p">(</span><span class="nx">addonName</span><span class="p">,</span> <span class="p">[</span><span class="dl">"</span><span class="s2">exports</span><span class="dl">"</span><span class="p">],</span> <span class="kd">function</span><span class="p">(</span><span class="nx">__exports__</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">library</span> <span class="o">=</span> <span class="nf">require</span><span class="p">(</span><span class="nx">index</span><span class="p">);</span>
<span class="nb">Object</span><span class="p">.</span><span class="nf">keys</span><span class="p">(</span><span class="nx">lf</span><span class="p">).</span><span class="nf">forEach</span><span class="p">(</span><span class="kd">function</span><span class="p">(</span><span class="nx">key</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">__exports__</span><span class="p">[</span><span class="nx">key</span><span class="p">]</span> <span class="o">=</span> <span class="nx">library</span><span class="p">[</span><span class="nx">key</span><span class="p">];</span>
<span class="p">});</span>
<span class="p">});</span>
<span class="c1">// Glue library to a global var</span>
<span class="nb">window</span><span class="p">.</span><span class="nx">EmberOffCanvasComponents</span> <span class="o">=</span> <span class="nb">window</span><span class="p">.</span><span class="nx">EOC</span> <span class="o">=</span> <span class="nf">require</span><span class="p">(</span><span class="nx">index</span><span class="p">);</span>
<span class="c1">// Register library items in the container</span>
<span class="kd">var</span> <span class="nx">shim</span> <span class="o">=</span> <span class="nx">addonName</span> <span class="o">+</span> <span class="dl">'</span><span class="s1">-shim</span><span class="dl">'</span><span class="p">;</span>
<span class="nb">window</span><span class="p">.</span><span class="nx">Ember</span><span class="p">.</span><span class="nx">Application</span><span class="p">.</span><span class="nf">initializer</span><span class="p">({</span>
<span class="na">name</span><span class="p">:</span> <span class="nx">shim</span><span class="p">,</span>
<span class="na">initialize</span><span class="p">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">container</span><span class="p">)</span> <span class="p">{</span>
<span class="nf">require</span><span class="p">(</span><span class="nx">shim</span><span class="p">).</span><span class="nf">initialize</span><span class="p">(</span><span class="nx">container</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">});</span>
<span class="c1">// Register Custom Events on the Application</span>
<span class="kd">var</span> <span class="nx">customEventsInitializer</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">ember-off-canvas-components/initializers/custom-events</span><span class="dl">'</span><span class="p">;</span>
<span class="nb">window</span><span class="p">.</span><span class="nx">Ember</span><span class="p">.</span><span class="nx">Application</span><span class="p">.</span><span class="nf">initializer</span><span class="p">({</span>
<span class="na">name</span><span class="p">:</span> <span class="nx">customEventsInitializer</span><span class="p">,</span>
<span class="na">initialize</span><span class="p">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">container</span><span class="p">,</span> <span class="nx">application</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">customEvents</span> <span class="o">=</span> <span class="nf">require</span><span class="p">(</span><span class="nx">customEventsInitializer</span><span class="p">);</span>
<span class="nx">customEvents</span><span class="p">.</span><span class="nf">initialize</span><span class="p">(</span><span class="nx">container</span><span class="p">,</span> <span class="nx">application</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">});</span>
</code></pre>
<h2>That's a Wrap</h2>
<p>Well now that the repository has tools available using <code>ember</code> and <code>npm run</code> on the command line. A few scripts were needed for post installation, pre-publishing, compiling CSS, and building a standalone release. Now, the <a href="proxy.php?url=https://github.com/pixelhandler/ember-off-canvas-components" target="_blank" rel="noopener noreferrer">Ember Off Canvas Components</a> addon can be found on <a href="proxy.php?url=http://www.emberaddons.com" target="_blank" rel="noopener noreferrer">Ember Addons</a> or with <code>npm search</code>. Developers can use the library with <code>npm install --save-dev pixelhandler/ember-off-canvas-components</code></p>
<p>Without too much effort, the micro library of Ember Components can be created, tested, released and consumed by application developers.</p>
<p>I'm looking forward to developing and sharing more components as Ember CLI addons.</p>
How to Use 404 Page in Your Ember.js Applicationhttps://pixelhandler.dev/posts/how-to-use-404-page-in-your-emberjs-application2014-08-29T00:00:00+00:002014-08-29T00:00:00+00:00Billy Heaton1. When the URL does not match any defined routes, the application should render a 404 page.
1. When a route's model hook has a call to this.store or Em.$.ajax and the result is a promise that is r...<h2>Cases when a 404 page is needed</h2>
<ol>
<li>When the URL does not match any defined routes, the application should render a 404 page.</li>
<li>When a route's <code>model</code> hook has a call to <code>this.store</code> or <code>Em.$.ajax</code> and the result is a promise that is rejected perhaps the application should show a 404 page. </li>
</ol>
<p>For example, when your adapter cannot find the resource that a route is expected to fetch. Ember.js routes may use an <code>error</code> action to transition to a 404 page. This will support a route which fails to transition due to a rejected promise caused by one or more of the route's hooks, e.g. <code>model</code>. </p>
<h2>Catch-all route</h2>
<p>To address the first case (above), when a given url doesn't match any route, define the last route in your router using a <a href="proxy.php?url=http://emberjs.com/guides/routing/defining-your-routes/#toc_wildcard-globbing-routes" target="_blank" rel="noopener noreferrer">wildcard globbing route</a>. </p>
<p>I setup a route for <strong><a href="proxy.php?url=https://github.com/pixelhandler/blog/blob/master/client/app/router.js" target="_blank" rel="noopener noreferrer">not-found</a></strong> in my router. The last route uses the wildcard <code>/*path</code> to catch any text string not already matched by other routes or resources. See the <code>'not-found'</code> route in the router example below.</p>
<pre class="highlight"><code><span class="nx">Router</span><span class="p">.</span><span class="nf">map</span><span class="p">(</span><span class="nf">function </span><span class="p">()</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nf">route</span><span class="p">(</span><span class="dl">'</span><span class="s1">about</span><span class="dl">'</span><span class="p">);</span>
<span class="k">this</span><span class="p">.</span><span class="nf">resource</span><span class="p">(</span><span class="dl">'</span><span class="s1">posts</span><span class="dl">'</span><span class="p">,</span> <span class="nf">function </span><span class="p">()</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nf">resource</span><span class="p">(</span><span class="dl">'</span><span class="s1">post</span><span class="dl">'</span><span class="p">,</span> <span class="p">{</span> <span class="na">path</span><span class="p">:</span> <span class="dl">'</span><span class="s1">:post_slug</span><span class="dl">'</span> <span class="p">});</span>
<span class="p">});</span>
<span class="k">this</span><span class="p">.</span><span class="nf">route</span><span class="p">(</span><span class="dl">'</span><span class="s1">not-found</span><span class="dl">'</span><span class="p">,</span> <span class="p">{</span> <span class="na">path</span><span class="p">:</span> <span class="dl">'</span><span class="s1">/*path</span><span class="dl">'</span> <span class="p">});</span>
<span class="p">});</span>
</code></pre>
<p>When the wildcard path matches a url that should result in the application rendering a 404 page, the (404) <a href="proxy.php?url=https://github.com/pixelhandler/blog/blob/master/client/app/routes/not-found.js" target="_blank" rel="noopener noreferrer">not-found route</a> utilizes the <code>redirect</code> hook in order to transition to the url <code>/not-found</code>. My app has a Route prototype (app/routes/not-found.js) which is mapped to the 'non-found' route:</p>
<pre class="highlight"><code><span class="k">import</span> <span class="nx">Ember</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">ember</span><span class="dl">'</span><span class="p">;</span>
<span class="k">export</span> <span class="k">default</span> <span class="nx">Ember</span><span class="p">.</span><span class="nx">Route</span><span class="p">.</span><span class="nf">extend</span><span class="p">({</span>
<span class="na">redirect</span><span class="p">:</span> <span class="nf">function </span><span class="p">()</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">url</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">router</span><span class="p">.</span><span class="nx">location</span><span class="p">.</span><span class="nf">formatURL</span><span class="p">(</span><span class="dl">'</span><span class="s1">/not-found</span><span class="dl">'</span><span class="p">);</span>
<span class="k">if </span><span class="p">(</span><span class="nb">window</span><span class="p">.</span><span class="nx">location</span><span class="p">.</span><span class="nx">pathname</span> <span class="o">!==</span> <span class="nx">url</span><span class="p">)</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nf">transitionTo</span><span class="p">(</span><span class="dl">'</span><span class="s1">/not-found</span><span class="dl">'</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">});</span>
</code></pre>
<p>The result is that the application renders a <code>not-found</code> template (templates/not-found.hbs) with a link to my archives page. </p>
<pre class="highlight"><code><h1>404 Not Found</h1>
<p>
Perhaps you have a link that has changed, see {{#link-to 'posts'}}Archives{{/link-to}}.
</p>
</code></pre>
<h2>Error in a route hook</h2>
<p>Any route having a hook (e.g. <code>model</code>, <code>beforeModel</code>, <code>afterModel</code>) that results in a rejected promise, can use the <code>error</code> action to transition to the 404. See the <a href="proxy.php?url=http://emberjs.com/api/classes/Ember.Route.html#event_error" target="_blank" rel="noopener noreferrer">Ember.Route error event</a> doc page.</p>
<p>Define the action, log the error and transition to the '/not-found' page</p>
<pre class="highlight"><code><span class="k">import</span> <span class="nx">Ember</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">ember</span><span class="dl">'</span><span class="p">;</span>
<span class="k">export</span> <span class="k">default</span> <span class="nx">Ember</span><span class="p">.</span><span class="nx">Route</span><span class="p">.</span><span class="nf">extend</span><span class="p">({</span>
<span class="na">actions</span><span class="p">:</span> <span class="p">{</span>
<span class="na">error</span><span class="p">:</span> <span class="nf">function </span><span class="p">(</span><span class="nx">error</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">Ember</span><span class="p">.</span><span class="nx">Logger</span><span class="p">.</span><span class="nf">error</span><span class="p">(</span><span class="nx">error</span><span class="p">);</span>
<span class="k">this</span><span class="p">.</span><span class="nf">transitionTo</span><span class="p">(</span><span class="dl">'</span><span class="s1">/not-found</span><span class="dl">'</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">});</span>
</code></pre>
<p>Here is my 404 page: <a href="proxy.php?url=http://pixelhandler.com/not-found" target="_blank" rel="noopener noreferrer">http://pixelhandler.com/not-found</a>.</p>
Real-time Data for an Ember.js Application using WebSocketshttps://pixelhandler.dev/posts/real-time-data-in-an-emberjs-application-with-websockets2014-08-20T00:00:00+00:002014-08-20T00:00:00+00:00Billy HeatonFollowing the release of Orbit.js, [Ember Orbit] was announced at the [Wicked Good Ember] conference. Again, I became excited about using smaller JSON Patch payloads (media type "application/json-p...<p>Following the release of Orbit.js, <a href="proxy.php?url=https://github.com/orbitjs/ember-orbit" target="_blank" rel="noopener noreferrer">Ember Orbit</a> was announced at the <a href="proxy.php?url=http://wickedgoodember.com/dan-gebhardt" target="_blank" rel="noopener noreferrer">Wicked Good Ember</a> conference. Again, I became excited about using smaller JSON Patch payloads (media type "application/json-patch+json"), see <a href="proxy.php?url=https://tools.ietf.org/html/rfc6902" target="_blank" rel="noopener noreferrer">RFC 6902</a>. I started a new branch and decided to support a REST source first; then later tackle a WebSocket source. I am a fan of the <a href="proxy.php?url=http://jsonapi.org/" target="_blank" rel="noopener noreferrer">JSON API</a> specification which does include <a href="proxy.php?url=http://jsonapi.org/format/#patch" target="_blank" rel="noopener noreferrer">patch support</a>. Once I had a JSONAPISource working with Ember Orbit on my branch I updated my SocketSource prototype (class) to follow the JSONAPISource prototype so they could share the same serializer. Also if the client's browser doesn't support a WebSocket or perhaps in the case my socket server gets wacky the application can initialize with the JSONAPISource instead of the SocketSource prototype as the connected source to the application's MemorySource.</p>
<h2>A Few Reasons to use WebSockets</h2>
<p><strong>Push support</strong> in a browser application can be used to provide real-time updates site visitors. Waiting for a client's next request or page refresh to provide updates works great when you expect frequent reloads. However, when an application is shipped as a single page (an Ember.js application) push support is attractive.</p>
<ul>
<li><p>Web Sockets (TCP)</p>
<ul>
<li>Potentially closer to real-time interations compared to REST</li>
<li>Ships with All modern (popular) browsers, see <a href="proxy.php?url=http://caniuse.com/#search=Socket" target="_blank" rel="noopener noreferrer">caniuse.com/#search=Socket</a></li>
<li>Gets around CORS</li>
</ul></li>
<li><p>Smaller payloads (partial representation of a resource)</p>
<ul>
<li><a href="proxy.php?url=https://tools.ietf.org/html/rfc6902" target="_blank" rel="noopener noreferrer">JSONPatch</a>, <a href="proxy.php?url=http://jsonpatch.com" target="_blank" rel="noopener noreferrer">jsonpatch.com</a></li>
</ul></li>
<li><p>Connecting multiple storage adapters</p>
<ul>
<li>Memory <-> localStorage <-> Web Socket (remote db)</li>
</ul></li>
<li><p>High availability (in a distributed computing system)</p>
<ul>
<li>Weak consistency with higher availability</li>
<li>Choose liveness (eventually there) over Safety (always right)</li>
</ul></li>
</ul>
<h2>JSON Patch</h2>
<p>See: <a href="proxy.php?url=http://jsonpatch.com" target="_blank" rel="noopener noreferrer">jsonpatch.com</a> ...</p>
<h3>Simple example</h3>
<p>The original document:</p>
<pre class="highlight"><code>{
"baz": "qux",
"foo": "bar"
}
</code></pre>
<p>The patch:</p>
<pre class="highlight"><code>[
{ "op": "replace", "path": "/baz", "value": "boo" },
{ "op": "add", "path": "/hello, "value": ["world"] },
{ "op": "remove, "path": "/foo}
]
</code></pre>
<p>The result:</p>
<pre class="highlight"><code>{
"baz": "boo",
"hello": ["world"]
}
</code></pre>
<h3>Operations</h3>
<p><em>Add, Remove, Replace, Copy, Copy, Test</em></p>
<h4>Add</h4>
<pre class="highlight"><code>{"op": "add", "path": "/biscuits/1", "value": {"name": "Ginger Nut"}}
</code></pre>
<p>Adds a value to an object or inserts it into an array. In the case of an array the value is inserted before the given index. The - character can be used instead of an index to insert at the end of an array.</p>
<h4>Remove</h4>
<pre class="highlight"><code>{"op": "remove", "path": "/biscuits"}
</code></pre>
<p>Removes a value from an object or array.</p>
<h4>Replace</h4>
<pre class="highlight"><code>{"op": "replace", "path": "/biscuits/0/name", "value": "Chocolate Digestive"}
</code></pre>
<p>Replaces a value, equivalent to a “remove” followed by an “add”.</p>
<h3>JSON Patch payloads used by this blog app</h3>
<p>An add operation...</p>
<pre class="highlight"><code>{"op":"add","path":"/posts/-","value":{"id":"a4d213d6-5595-4686-9817-169a7eddbda1","slug":"test","title":"test title","date":"2014-10-29","excerpt":"test excerpt text","body":"test body text","links":{"author":null}}}
{"path":"/posts/a4d213d6-5595-4686-9817-169a7eddbda1/links/author","op":"add","value":"5c9b62ec-1569-448b-912a-97e6d62f493e"}
{"path":"/authors/5c9b62ec-1569-448b-912a-97e6d62f493e/links/posts/-","op":"add","value":"a4d213d6-5595-4686-9817-169a7eddbda1"}
</code></pre>
<p>A delete operation...</p>
<pre class="highlight"><code>{"op":"remove","path":"/posts/341207e0-cfd9-4d3a-a5ab-d2268ab2e472/links/author"}
{"op":"remove","path":"/authors/5c9b62ec-1569-448b-912a-97e6d62f493e/links/posts/341207e0-cfd9-4d3a-a5ab-d2268ab2e472"}
{"op":"remove","path":"/posts/341207e0-cfd9-4d3a-a5ab-d2268ab2e472"}
</code></pre>
<p>Compare with the JSON API payloads</p>
<ul>
<li><a href="proxy.php?url=http://pixelhandler.com/api/posts?limit=1" target="_blank" rel="noopener noreferrer">http://pixelhandler.com/api/posts?limit=1</a></li>
<li><a href="proxy.php?url=http://pixelhandler.com/api/authors?limit=1" target="_blank" rel="noopener noreferrer">http://pixelhandler.com/api/authors?limit=1</a></li>
</ul>
<h2><a href="proxy.php?url=https://github.com/orbitjs/orbit.js" target="_blank" rel="noopener noreferrer">Orbit.js</a></h2>
<p>Let's take a look at the [ember-orbit-example] <a href="proxy.php?url=http://localhost:9000" target="_blank" rel="noopener noreferrer">demo app</a>.</p>
<p>Watch Dan give an intoduction to Orbit.js. (YouTube video from the Jan '14 Boston Ember Meetup)</p>
<ul>
<li><a href="proxy.php?url=https://www.youtube.com/watch?v=IAwk_mF-dWo" target="_blank" rel="noopener noreferrer">Introduction to Orbit.js by Dan Gebhardt</a></li>
<li><a href="proxy.php?url=https://speakerdeck.com/dgeb/introducing-orbit-dot-js" target="_blank" rel="noopener noreferrer">introducing Orbit.js slides</a></li>
</ul>
<h3>How Orbit works</h3>
<ul>
<li>Orbit requires that every data source support one or more common interfaces. These interfaces define how data can be both accessed and transformed.</li>
<li>The methods for accessing and transforming data return promises.</li>
<li>Multiple data sources can be involved in a single action</li>
</ul>
<p><strong>Orbit.js uses JSON / JSONPatch by default.</strong></p>
<ul>
<li>Sends partial data</li>
<li>Uses multiple stores</li>
</ul>
<h3>Orbit interfaces:</h3>
<ol>
<li>Requestable
<ul>
<li>for managing requests for data via methods such as <code>find</code>, <code>create</code>, <code>update</code> and <code>destroy</code></li>
</ul></li>
<li>Transformable
<ul>
<li>Keep data sources in sync through low level transformations (using JSON PATCH spec)</li>
<li><code>transform</code> is the method your data source (prototype) object needs to implement</li>
</ul></li>
</ol>
<h4>Requestable</h4>
<p>Events associated with an action:</p>
<ul>
<li><code>assistFind</code>, <code>rescueFind</code>, <code>didFind</code>, <code>didNotFind</code></li>
</ul>
<h4>Transformable</h4>
<p>A single method, <code>transform</code>, which can be used to change the contents of a source.</p>
<pre class="highlight"><code>{op: 'add', path: 'planet/1', value: {__id: 1, name: 'Jupiter', classification: 'gas giant'}
{op: 'replace', path: 'planet/1/name', value: 'Earth'}
{op: 'remove', path: 'planet/1'}
</code></pre>
<h3>TransformConnector</h3>
<p>A TransformConnector watches a transformable source and propagates any transforms to a transformable target.</p>
<p>Each connector is "one way", so bi-directional synchronization between sources requires the creation of two connectors.</p>
<h3>RequestConnector</h3>
<p>A RequestConnector observes requests made to a primary source and allows a secondary source to either "assist" or "rescue" those requests.</p>
<p>The mode of a RequestConnector can be either "rescue" or "assist" ("rescue" is the default).</p>
<h3>Document</h3>
<p>Document is a complete implementation of the JSON PATCH spec detailed in RFC 6902.</p>
<p>It can be manipulated via a transform method that accepts an operation, or with methods <code>add</code>, <code>remove</code>, <code>replace</code>, <code>move</code>, <code>copy</code> and <code>test</code>.</p>
<p>Data at a particular path can be retrieved from a <code>Document</code> with <code>retrieve()</code>.</p>
<h2><a href="proxy.php?url=https://github.com/orbitjs/ember-orbit" target="_blank" rel="noopener noreferrer">Ember Orbit</a></h2>
<p>"A library that integrates Orbit.js with Ember.js to provide flexibility and control in your application's data layer" </p>
<ul>
<li>Video: <a href="proxy.php?url=https://www.youtube.com/watch?v=omc4pnXv1Ds" target="_blank" rel="noopener noreferrer">Building Autonomous Web Applications with Ember and Orbit</a></li>
</ul>
<p>This project uses <a href="proxy.php?url=http://www.ember-cli.com/" target="_blank" rel="noopener noreferrer">ember-cli</a>, to the modules below use ES6 syntax, see the <a href="proxy.php?url=https://people.mozilla.org/%7Ejorendorff/es6-draft.html" target="_blank" rel="noopener noreferrer">ES6 draft</a>.</p>
<h3>Initializer</h3>
<p>Configure Ember-Orbit with an application initializer that sets up Orbit and registers a "main" store and schema to be available in routes and controllers.</p>
<p><a href="proxy.php?url=https://github.com/pixelhandler/blog/blob/master/client/app/initializers/ember-orbit.js" target="_blank" rel="noopener noreferrer">initializers/ember-orbit.js</a></p>
<pre class="highlight"><code><span class="k">import</span> <span class="nx">Orbit</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">orbit</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="nx">EO</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">ember-orbit</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="nx">JSONAPISource</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">orbit-common/jsonapi-source</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="nx">ApplicationSerializer</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../serializers/application</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="nx">SocketSource</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../adapters/socket-source</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="nx">Ember</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">ember</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="nx">config</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../config/environment</span><span class="dl">'</span><span class="p">;</span>
<span class="nx">Orbit</span><span class="p">.</span><span class="nb">Promise</span> <span class="o">=</span> <span class="nx">Orbit</span><span class="p">.</span><span class="nb">Promise</span> <span class="o">||</span> <span class="nx">Ember</span><span class="p">.</span><span class="nx">RSVP</span><span class="p">.</span><span class="nb">Promise</span><span class="p">;</span>
<span class="kd">function</span> <span class="nf">jsonApiStore</span><span class="p">()</span> <span class="p">{</span>
<span class="nx">Orbit</span><span class="p">.</span><span class="nx">ajax</span> <span class="o">=</span> <span class="nx">Ember</span><span class="p">.</span><span class="nx">$</span><span class="p">.</span><span class="nx">ajax</span><span class="p">;</span>
<span class="k">return</span> <span class="nx">EO</span><span class="p">.</span><span class="nx">Store</span><span class="p">.</span><span class="nf">extend</span><span class="p">({</span>
<span class="na">orbitSourceClass</span><span class="p">:</span> <span class="nx">JSONAPISource</span><span class="p">,</span>
<span class="na">orbitSourceOptions</span><span class="p">:</span> <span class="p">{</span>
<span class="na">host</span><span class="p">:</span> <span class="nx">config</span><span class="p">.</span><span class="nx">APP</span><span class="p">.</span><span class="nx">API_HOST</span><span class="p">,</span>
<span class="na">namespace</span><span class="p">:</span> <span class="nx">config</span><span class="p">.</span><span class="nx">APP</span><span class="p">.</span><span class="nx">API_PATH</span><span class="p">,</span>
<span class="na">SerializerClass</span><span class="p">:</span> <span class="nx">ApplicationSerializer</span><span class="p">,</span>
<span class="na">usePatch</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="p">}</span>
<span class="p">});</span>
<span class="p">}</span>
<span class="kd">function</span> <span class="nf">socketStore</span><span class="p">()</span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">EO</span><span class="p">.</span><span class="nx">Store</span><span class="p">.</span><span class="nf">extend</span><span class="p">({</span>
<span class="na">orbitSourceClass</span><span class="p">:</span> <span class="nx">SocketSource</span><span class="p">,</span>
<span class="na">orbitSourceOptions</span><span class="p">:</span> <span class="p">{</span>
<span class="na">host</span><span class="p">:</span> <span class="nx">config</span><span class="p">.</span><span class="nx">APP</span><span class="p">.</span><span class="nx">SOCKET_URL</span><span class="p">,</span>
<span class="na">SerializerClass</span><span class="p">:</span> <span class="nx">ApplicationSerializer</span><span class="p">,</span>
<span class="na">usePatch</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="p">}</span>
<span class="p">});</span>
<span class="p">}</span>
<span class="kd">var</span> <span class="nx">Schema</span> <span class="o">=</span> <span class="nx">EO</span><span class="p">.</span><span class="nx">Schema</span><span class="p">.</span><span class="nf">extend</span><span class="p">({</span>
<span class="na">idField</span><span class="p">:</span> <span class="dl">'</span><span class="s1">id</span><span class="dl">'</span><span class="p">,</span>
<span class="na">init</span><span class="p">:</span> <span class="nf">function </span><span class="p">(</span><span class="nx">options</span><span class="p">)</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nf">_super</span><span class="p">(</span><span class="nx">options</span><span class="p">);</span>
<span class="k">this</span><span class="p">.</span><span class="nx">_schema</span><span class="p">.</span><span class="nx">meta</span> <span class="o">=</span> <span class="nx">Ember</span><span class="p">.</span><span class="nb">Map</span><span class="p">.</span><span class="nf">create</span><span class="p">();</span>
<span class="p">}</span>
<span class="p">});</span>
<span class="k">export</span> <span class="k">default</span> <span class="p">{</span>
<span class="na">name</span><span class="p">:</span> <span class="dl">'</span><span class="s1">ember-orbit</span><span class="dl">'</span><span class="p">,</span>
<span class="na">after</span><span class="p">:</span> <span class="dl">'</span><span class="s1">socket</span><span class="dl">'</span><span class="p">,</span>
<span class="na">initialize</span><span class="p">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">container</span><span class="p">,</span> <span class="nx">application</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">application</span><span class="p">.</span><span class="nf">register</span><span class="p">(</span><span class="dl">'</span><span class="s1">schema:main</span><span class="dl">'</span><span class="p">,</span> <span class="nx">Schema</span><span class="p">);</span>
<span class="nx">application</span><span class="p">.</span><span class="nf">register</span><span class="p">(</span><span class="dl">'</span><span class="s1">store:main</span><span class="dl">'</span><span class="p">,</span> <span class="nx">EO</span><span class="p">.</span><span class="nx">Store</span><span class="p">);</span>
<span class="k">if </span><span class="p">(</span><span class="nf">notPrerenderService</span><span class="p">()</span> <span class="o">&&</span> <span class="nf">canUseSocket</span><span class="p">(</span><span class="nx">container</span><span class="p">))</span> <span class="p">{</span>
<span class="nx">application</span><span class="p">.</span><span class="nf">register</span><span class="p">(</span><span class="dl">'</span><span class="s1">store:secondary</span><span class="dl">'</span><span class="p">,</span> <span class="nf">socketStore</span><span class="p">());</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="nx">application</span><span class="p">.</span><span class="nf">register</span><span class="p">(</span><span class="dl">'</span><span class="s1">store:secondary</span><span class="dl">'</span><span class="p">,</span> <span class="nf">jsonApiStore</span><span class="p">());</span>
<span class="p">}</span>
<span class="nf">connectSources</span><span class="p">(</span><span class="nx">container</span><span class="p">);</span>
<span class="nx">application</span><span class="p">.</span><span class="nf">inject</span><span class="p">(</span><span class="dl">'</span><span class="s1">controller</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">store</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">store:main</span><span class="dl">'</span><span class="p">);</span>
<span class="nx">application</span><span class="p">.</span><span class="nf">inject</span><span class="p">(</span><span class="dl">'</span><span class="s1">route</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">store</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">store:main</span><span class="dl">'</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">};</span>
<span class="kd">function</span> <span class="nf">notPrerenderService</span><span class="p">()</span> <span class="p">{</span>
<span class="k">return</span> <span class="nb">window</span><span class="p">.</span><span class="nb">navigator</span><span class="p">.</span><span class="nx">userAgent</span><span class="p">.</span><span class="nf">match</span><span class="p">(</span><span class="sr">/Prerender/</span><span class="p">)</span> <span class="o">===</span> <span class="kc">null</span><span class="p">;</span>
<span class="p">}</span>
<span class="kd">function</span> <span class="nf">canUseSocket</span><span class="p">(</span><span class="nx">container</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="nb">window</span><span class="p">.</span><span class="nx">WebSocket</span> <span class="o">&&</span> <span class="nx">container</span><span class="p">.</span><span class="nf">lookup</span><span class="p">(</span><span class="dl">'</span><span class="s1">socket:main</span><span class="dl">'</span><span class="p">);</span>
<span class="p">}</span>
<span class="kd">function</span> <span class="nf">connectSources</span><span class="p">(</span><span class="nx">container</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">primarySource</span> <span class="o">=</span> <span class="nx">container</span><span class="p">.</span><span class="nf">lookup</span><span class="p">(</span><span class="dl">'</span><span class="s1">store:main</span><span class="dl">'</span><span class="p">).</span><span class="nx">orbitSource</span><span class="p">;</span>
<span class="kd">var</span> <span class="nx">secondarySource</span> <span class="o">=</span> <span class="nx">container</span><span class="p">.</span><span class="nf">lookup</span><span class="p">(</span><span class="dl">'</span><span class="s1">store:secondary</span><span class="dl">'</span><span class="p">).</span><span class="nx">orbitSource</span><span class="p">;</span>
<span class="c1">// Connect (using default blocking strategy)</span>
<span class="nf">setupConnectors</span><span class="p">(</span><span class="nx">primarySource</span><span class="p">,</span> <span class="nx">secondarySource</span><span class="p">);</span>
<span class="p">}</span>
<span class="kd">function</span> <span class="nf">setupConnectors</span><span class="p">(</span><span class="nx">primary</span><span class="p">,</span> <span class="nx">secondary</span><span class="cm">/*, local*/</span><span class="p">)</span> <span class="p">{</span>
<span class="k">new</span> <span class="nx">Orbit</span><span class="p">.</span><span class="nc">TransformConnector</span><span class="p">(</span><span class="nx">primary</span><span class="p">,</span> <span class="nx">secondary</span><span class="p">);</span>
<span class="k">new</span> <span class="nx">Orbit</span><span class="p">.</span><span class="nc">TransformConnector</span><span class="p">(</span><span class="nx">secondary</span><span class="p">,</span> <span class="nx">primary</span><span class="p">);</span>
<span class="nx">primary</span><span class="p">.</span><span class="nf">on</span><span class="p">(</span><span class="dl">'</span><span class="s1">assistFind</span><span class="dl">'</span><span class="p">,</span> <span class="nx">secondary</span><span class="p">.</span><span class="nx">find</span><span class="p">);</span>
<span class="p">}</span>
</code></pre>
<h3>Sources</h3>
<p>"Source are very thin wrappers over Orbit sources"</p>
<p><a href="proxy.php?url=https://github.com/pixelhandler/blog/blob/master/client/app/adapters/socket-source.js" target="_blank" rel="noopener noreferrer">adapters/socket-source.js</a></p>
<pre class="highlight"><code><span class="k">import</span> <span class="nx">Ember</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">ember</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="nx">Orbit</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">orbit</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="nx">OC</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">orbit-common</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="nx">SocketService</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../services/socket</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="nx">JSONAPISource</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">orbit-common/jsonapi-source</span><span class="dl">'</span><span class="p">;</span>
<span class="nx">Orbit</span><span class="p">.</span><span class="nb">Promise</span> <span class="o">=</span> <span class="nx">Orbit</span><span class="p">.</span><span class="nb">Promise</span> <span class="o">||</span> <span class="nx">Ember</span><span class="p">.</span><span class="nx">RSVP</span><span class="p">.</span><span class="nb">Promise</span><span class="p">;</span>
<span class="kd">var</span> <span class="nx">SocketSource</span> <span class="o">=</span> <span class="nx">JSONAPISource</span><span class="p">.</span><span class="nf">extend</span><span class="p">({</span>
<span class="na">init</span><span class="p">:</span> <span class="nf">function </span><span class="p">(</span><span class="nx">schema</span><span class="p">,</span> <span class="nx">options</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">Orbit</span><span class="p">.</span><span class="nf">assert</span><span class="p">(</span><span class="dl">'</span><span class="s1">SocketSource requires SocketService be defined</span><span class="dl">'</span><span class="p">,</span> <span class="nx">SocketService</span><span class="p">);</span>
<span class="nx">Orbit</span><span class="p">.</span><span class="nf">assert</span><span class="p">(</span><span class="dl">'</span><span class="s1">SocketSource requires Orbit.Promise be defined</span><span class="dl">'</span><span class="p">,</span> <span class="nx">Orbit</span><span class="p">.</span><span class="nb">Promise</span><span class="p">);</span>
<span class="nx">Orbit</span><span class="p">.</span><span class="nf">assert</span><span class="p">(</span><span class="dl">'</span><span class="s1">SocketSource only supports usePatch option</span><span class="dl">'</span><span class="p">,</span> <span class="k">this</span><span class="p">.</span><span class="nx">usePatch</span><span class="p">);</span>
<span class="k">this</span><span class="p">.</span><span class="nx">_socket</span> <span class="o">=</span> <span class="nx">SocketService</span><span class="p">.</span><span class="nf">create</span><span class="p">();</span>
<span class="k">this</span><span class="p">.</span><span class="nf">initSerializer</span><span class="p">(</span><span class="nx">schema</span><span class="p">,</span> <span class="nx">options</span><span class="p">);</span>
<span class="c1">// not calling super, instead calling template/abstract prototype init method</span>
<span class="k">return</span> <span class="nx">OC</span><span class="p">.</span><span class="nx">Source</span><span class="p">.</span><span class="nx">prototype</span><span class="p">.</span><span class="nx">init</span><span class="p">.</span><span class="nf">apply</span><span class="p">(</span><span class="k">this</span><span class="p">,</span> <span class="nx">arguments</span><span class="p">);</span>
<span class="p">},</span>
<span class="na">initSerializer</span><span class="p">:</span> <span class="nf">function </span><span class="p">(</span><span class="nx">schema</span><span class="p">,</span> <span class="nx">options</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">// See JSONAPISource</span>
<span class="k">this</span><span class="p">.</span><span class="nx">SerializerClass</span> <span class="o">=</span> <span class="nx">options</span><span class="p">.</span><span class="nx">SerializerClass</span> <span class="o">||</span> <span class="k">this</span><span class="p">.</span><span class="nx">SerializerClass</span><span class="p">;</span>
<span class="k">if </span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">SerializerClass</span> <span class="o">&&</span> <span class="k">this</span><span class="p">.</span><span class="nx">SerializerClass</span><span class="p">.</span><span class="nx">wrappedFunction</span><span class="p">)</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nx">SerializerClass</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">SerializerClass</span><span class="p">.</span><span class="nx">wrappedFunction</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">this</span><span class="p">.</span><span class="nx">serializer</span> <span class="o">=</span> <span class="k">new</span> <span class="k">this</span><span class="p">.</span><span class="nc">SerializerClass</span><span class="p">(</span><span class="nx">schema</span><span class="p">);</span>
<span class="p">},</span>
<span class="c1">// using JSONPatch via WebSocket</span>
<span class="na">usePatch</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="c1">// Requestable interface implementation</span>
<span class="na">_find</span><span class="p">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">type</span><span class="p">,</span> <span class="nx">id</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if </span><span class="p">(</span><span class="nx">id</span> <span class="o">&&</span> <span class="p">(</span><span class="k">typeof</span> <span class="nx">id</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">number</span><span class="dl">'</span> <span class="o">||</span> <span class="k">typeof</span> <span class="nx">id</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">string</span><span class="dl">'</span><span class="p">))</span> <span class="p">{</span>
<span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nf">_findOne</span><span class="p">(</span><span class="nx">type</span><span class="p">,</span> <span class="nx">id</span><span class="p">);</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nf">_findQuery</span><span class="p">(</span><span class="nx">type</span><span class="p">,</span> <span class="nx">id</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">},</span>
<span class="na">_findLink</span><span class="p">:</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nf">error</span><span class="p">(</span><span class="dl">'</span><span class="s1">TODO, SocketSource#_findLink not supported yet</span><span class="dl">'</span><span class="p">);</span>
<span class="p">},</span>
<span class="c1">// Requestable Internals</span>
<span class="na">_findOne</span><span class="p">:</span> <span class="nf">function </span><span class="p">(</span><span class="nx">type</span><span class="p">,</span> <span class="nx">id</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">query</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nf">_queryFactory</span><span class="p">(</span><span class="nx">type</span><span class="p">,</span> <span class="p">{</span> <span class="na">id</span><span class="p">:</span> <span class="nx">id</span> <span class="p">});</span>
<span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nf">_remoteFind</span><span class="p">(</span><span class="dl">'</span><span class="s1">find</span><span class="dl">'</span><span class="p">,</span> <span class="nx">type</span><span class="p">,</span> <span class="nx">query</span><span class="p">);</span>
<span class="p">},</span>
<span class="na">_findMany</span><span class="p">:</span> <span class="nf">function </span><span class="p">()</span> <span class="p">{</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nc">Error</span><span class="p">(</span><span class="dl">'</span><span class="s1">SocketSource#_findMany not supported</span><span class="dl">'</span><span class="p">);</span>
<span class="p">},</span>
<span class="na">_findQuery</span><span class="p">:</span> <span class="nf">function </span><span class="p">(</span><span class="nx">type</span><span class="p">,</span> <span class="nx">query</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">query</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nf">_queryFactory</span><span class="p">(</span><span class="nx">type</span><span class="p">,</span> <span class="nx">query</span><span class="p">);</span>
<span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nf">_remoteFind</span><span class="p">(</span><span class="dl">'</span><span class="s1">findQuery</span><span class="dl">'</span><span class="p">,</span> <span class="nx">type</span><span class="p">,</span> <span class="nx">query</span><span class="p">);</span>
<span class="p">},</span>
<span class="na">_remoteFind</span><span class="p">:</span> <span class="nf">function </span><span class="p">(</span><span class="nx">channel</span><span class="p">,</span> <span class="nx">type</span><span class="p">,</span> <span class="nx">query</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">root</span> <span class="o">=</span> <span class="nf">pluralize</span><span class="p">(</span><span class="nx">type</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">id</span> <span class="o">=</span> <span class="nx">query</span><span class="p">.</span><span class="nx">id</span><span class="p">;</span>
<span class="nx">query</span> <span class="o">=</span> <span class="nx">JSON</span><span class="p">.</span><span class="nf">stringify</span><span class="p">(</span><span class="nx">query</span><span class="p">);</span>
<span class="c1">// handle promise resolution serially, pass off return to next then handler</span>
<span class="kd">var</span> <span class="nx">records</span><span class="p">;</span>
<span class="k">return</span> <span class="k">new</span> <span class="nx">Orbit</span><span class="p">.</span><span class="nc">Promise</span><span class="p">(</span><span class="kd">function</span> <span class="nf">doFind</span><span class="p">(</span><span class="nx">resolve</span><span class="p">,</span> <span class="nx">reject</span><span class="p">)</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nx">_socket</span><span class="p">.</span><span class="nf">emit</span><span class="p">(</span><span class="nx">channel</span><span class="p">,</span> <span class="nx">query</span><span class="p">,</span> <span class="kd">function</span> <span class="nf">didFind</span><span class="p">(</span><span class="nx">raw</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if </span><span class="p">(</span><span class="nx">raw</span><span class="p">.</span><span class="nx">errors</span> <span class="o">||</span> <span class="o">!</span><span class="nx">raw</span><span class="p">[</span><span class="nx">root</span><span class="p">])</span> <span class="p">{</span>
<span class="nf">reject</span><span class="p">(</span><span class="nx">raw</span><span class="p">.</span><span class="nx">errors</span><span class="p">);</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="nf">resolve</span><span class="p">(</span><span class="nx">raw</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">});</span>
<span class="p">}.</span><span class="nf">bind</span><span class="p">(</span><span class="k">this</span><span class="p">))</span>
<span class="p">.</span><span class="nf">then</span><span class="p">(</span><span class="kd">function</span> <span class="nf">doProcess</span><span class="p">(</span><span class="nx">raw</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nf">deserialize</span><span class="p">(</span><span class="nx">type</span><span class="p">,</span> <span class="nx">id</span><span class="p">,</span> <span class="nx">raw</span><span class="p">);</span>
<span class="p">}.</span><span class="nf">bind</span><span class="p">(</span><span class="k">this</span><span class="p">))</span>
<span class="p">.</span><span class="nf">then</span><span class="p">(</span><span class="nf">function </span><span class="p">(</span><span class="nx">data</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">records</span> <span class="o">=</span> <span class="nx">data</span><span class="p">;</span>
<span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nf">settleTransforms</span><span class="p">();</span>
<span class="p">}.</span><span class="nf">bind</span><span class="p">(</span><span class="k">this</span><span class="p">))</span>
<span class="p">.</span><span class="nf">then</span><span class="p">(</span><span class="nf">function </span><span class="p">()</span> <span class="p">{</span>
<span class="c1">// finally send back the records</span>
<span class="k">return</span> <span class="nx">records</span><span class="p">;</span>
<span class="p">})</span>
<span class="p">.</span><span class="k">catch</span><span class="p">(</span><span class="kd">function</span> <span class="nf">onError</span><span class="p">(</span><span class="nx">error</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nf">error</span><span class="p">(</span><span class="dl">'</span><span class="s1">SocketSource#_remoteFind Error w/ query: </span><span class="dl">'</span> <span class="o">+</span> <span class="nx">query</span><span class="p">);</span>
<span class="nx">console</span><span class="p">.</span><span class="nf">error</span><span class="p">(</span><span class="nx">error</span><span class="p">);</span>
<span class="p">});</span>
<span class="p">},</span>
<span class="na">_queryFactory</span><span class="p">:</span> <span class="nf">function </span><span class="p">(</span><span class="nx">type</span><span class="p">,</span> <span class="nx">query</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">query</span> <span class="o">=</span> <span class="nx">query</span> <span class="o">||</span> <span class="p">{};</span>
<span class="nx">query</span><span class="p">.</span><span class="nx">resource</span> <span class="o">=</span> <span class="nx">query</span><span class="p">.</span><span class="nx">resource</span> <span class="o">||</span> <span class="nf">pluralize</span><span class="p">(</span><span class="nx">type</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">attrs</span> <span class="o">=</span> <span class="nx">Ember</span><span class="p">.</span><span class="nb">String</span><span class="p">.</span><span class="nf">w</span><span class="p">(</span><span class="dl">'</span><span class="s1">limit offset sortBy order resource withFields</span><span class="dl">'</span><span class="p">);</span>
<span class="nx">attrs</span><span class="p">.</span><span class="nf">forEach</span><span class="p">(</span><span class="nf">function </span><span class="p">(</span><span class="nx">attr</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">query</span><span class="p">[</span><span class="nx">attr</span><span class="p">]</span> <span class="o">=</span> <span class="nx">query</span><span class="p">[</span><span class="nx">attr</span><span class="p">]</span> <span class="o">||</span> <span class="nx">Ember</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="k">this</span><span class="p">,</span> <span class="nx">attr</span><span class="p">);</span>
<span class="p">}.</span><span class="nf">bind</span><span class="p">(</span><span class="k">this</span><span class="p">));</span>
<span class="k">return</span> <span class="nx">query</span><span class="p">;</span>
<span class="p">},</span>
<span class="c1">// Transformable Internals</span>
<span class="na">_transformAdd</span><span class="p">:</span> <span class="nf">function </span><span class="p">(</span><span class="nx">operation</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">type</span> <span class="o">=</span> <span class="nx">operation</span><span class="p">.</span><span class="nx">path</span><span class="p">[</span><span class="mi">0</span><span class="p">];</span>
<span class="kd">var</span> <span class="nx">id</span> <span class="o">=</span> <span class="nx">operation</span><span class="p">.</span><span class="nx">path</span><span class="p">[</span><span class="mi">1</span><span class="p">];</span>
<span class="kd">var</span> <span class="nx">remoteOp</span> <span class="o">=</span> <span class="p">{</span>
<span class="na">op</span><span class="p">:</span> <span class="dl">'</span><span class="s1">add</span><span class="dl">'</span><span class="p">,</span>
<span class="na">path</span><span class="p">:</span> <span class="dl">'</span><span class="s1">/</span><span class="dl">'</span> <span class="o">+</span> <span class="nf">pluralize</span><span class="p">(</span><span class="nx">type</span><span class="p">)</span> <span class="o">+</span> <span class="dl">'</span><span class="s1">/-</span><span class="dl">'</span><span class="p">,</span>
<span class="na">value</span><span class="p">:</span> <span class="k">this</span><span class="p">.</span><span class="nx">serializer</span><span class="p">.</span><span class="nf">serializeRecord</span><span class="p">(</span><span class="nx">type</span><span class="p">,</span> <span class="nx">operation</span><span class="p">.</span><span class="nx">value</span><span class="p">)</span>
<span class="p">};</span>
<span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nf">_remotePatch</span><span class="p">(</span><span class="nx">type</span><span class="p">,</span> <span class="nx">id</span><span class="p">,</span> <span class="nx">remoteOp</span><span class="p">);</span>
<span class="p">},</span>
<span class="na">_transformReplace</span><span class="p">:</span> <span class="nf">function </span><span class="p">(</span><span class="nx">operation</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">type</span> <span class="o">=</span> <span class="nx">operation</span><span class="p">.</span><span class="nx">path</span><span class="p">[</span><span class="mi">0</span><span class="p">];</span>
<span class="nx">operation</span><span class="p">.</span><span class="nx">path</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">=</span> <span class="nf">pluralize</span><span class="p">(</span><span class="nx">type</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">id</span> <span class="o">=</span> <span class="nx">operation</span><span class="p">.</span><span class="nx">path</span><span class="p">[</span><span class="mi">1</span><span class="p">];</span>
<span class="kd">var</span> <span class="nx">remoteOp</span> <span class="o">=</span> <span class="p">{</span>
<span class="na">op</span><span class="p">:</span> <span class="dl">'</span><span class="s1">replace</span><span class="dl">'</span><span class="p">,</span>
<span class="na">path</span><span class="p">:</span> <span class="dl">'</span><span class="s1">/</span><span class="dl">'</span> <span class="o">+</span> <span class="nx">operation</span><span class="p">.</span><span class="nx">path</span><span class="p">.</span><span class="nf">join</span><span class="p">(</span><span class="dl">'</span><span class="s1">/</span><span class="dl">'</span><span class="p">),</span>
<span class="na">value</span><span class="p">:</span> <span class="k">this</span><span class="p">.</span><span class="nx">serializer</span><span class="p">.</span><span class="nf">serializeRecord</span><span class="p">(</span><span class="nx">type</span><span class="p">,</span> <span class="nx">operation</span><span class="p">.</span><span class="nx">value</span><span class="p">)</span>
<span class="p">};</span>
<span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nf">_remotePatch</span><span class="p">(</span><span class="nx">type</span><span class="p">,</span> <span class="nx">id</span><span class="p">,</span> <span class="nx">remoteOp</span><span class="p">);</span>
<span class="p">},</span>
<span class="na">_transformRemove</span><span class="p">:</span> <span class="nf">function </span><span class="p">(</span><span class="nx">operation</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">type</span> <span class="o">=</span> <span class="nx">operation</span><span class="p">.</span><span class="nx">path</span><span class="p">[</span><span class="mi">0</span><span class="p">];</span>
<span class="nx">operation</span><span class="p">.</span><span class="nx">path</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">=</span> <span class="nf">pluralize</span><span class="p">(</span><span class="nx">type</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">id</span> <span class="o">=</span> <span class="nx">operation</span><span class="p">.</span><span class="nx">path</span><span class="p">[</span><span class="mi">1</span><span class="p">];</span>
<span class="kd">var</span> <span class="nx">path</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">/</span><span class="dl">'</span> <span class="o">+</span> <span class="nx">operation</span><span class="p">.</span><span class="nx">path</span><span class="p">.</span><span class="nf">join</span><span class="p">(</span><span class="dl">'</span><span class="s1">/</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">remoteOp</span> <span class="o">=</span> <span class="p">{</span> <span class="na">op</span><span class="p">:</span> <span class="dl">'</span><span class="s1">remove</span><span class="dl">'</span><span class="p">,</span> <span class="na">path</span><span class="p">:</span> <span class="nx">path</span> <span class="p">};</span>
<span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nf">_remotePatch</span><span class="p">(</span><span class="nx">type</span><span class="p">,</span> <span class="nx">id</span><span class="p">,</span> <span class="nx">remoteOp</span><span class="p">);</span>
<span class="p">},</span>
<span class="na">_transformUpdateAttribute</span><span class="p">:</span> <span class="nf">function </span><span class="p">(</span><span class="nx">operation</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">type</span> <span class="o">=</span> <span class="nx">operation</span><span class="p">.</span><span class="nx">path</span><span class="p">[</span><span class="mi">0</span><span class="p">];</span>
<span class="nx">operation</span><span class="p">.</span><span class="nx">path</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">=</span> <span class="nf">pluralize</span><span class="p">(</span><span class="nx">type</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">id</span> <span class="o">=</span> <span class="nx">operation</span><span class="p">.</span><span class="nx">path</span><span class="p">[</span><span class="mi">1</span><span class="p">];</span>
<span class="kd">var</span> <span class="nx">remoteOp</span> <span class="o">=</span> <span class="p">{</span>
<span class="na">op</span><span class="p">:</span> <span class="dl">'</span><span class="s1">replace</span><span class="dl">'</span><span class="p">,</span>
<span class="na">path</span><span class="p">:</span> <span class="dl">'</span><span class="s1">/</span><span class="dl">'</span> <span class="o">+</span> <span class="nx">operation</span><span class="p">.</span><span class="nx">path</span><span class="p">.</span><span class="nf">join</span><span class="p">(</span><span class="dl">'</span><span class="s1">/</span><span class="dl">'</span><span class="p">),</span> <span class="c1">// includes attr in path</span>
<span class="na">value</span><span class="p">:</span> <span class="nx">operation</span><span class="p">.</span><span class="nx">value</span>
<span class="p">};</span>
<span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nf">_remotePatch</span><span class="p">(</span><span class="nx">type</span><span class="p">,</span> <span class="nx">id</span><span class="p">,</span> <span class="nx">remoteOp</span><span class="p">);</span>
<span class="p">},</span>
<span class="na">_transformAddLink</span><span class="p">:</span> <span class="nf">function </span><span class="p">(</span><span class="nx">operation</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">type</span> <span class="o">=</span> <span class="nx">operation</span><span class="p">.</span><span class="nx">path</span><span class="p">[</span><span class="mi">0</span><span class="p">];</span>
<span class="nx">operation</span><span class="p">.</span><span class="nx">path</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">=</span> <span class="nf">pluralize</span><span class="p">(</span><span class="nx">type</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">id</span> <span class="o">=</span> <span class="nx">operation</span><span class="p">.</span><span class="nx">path</span><span class="p">[</span><span class="mi">1</span><span class="p">];</span>
<span class="kd">var</span> <span class="nx">link</span> <span class="o">=</span> <span class="nx">operation</span><span class="p">.</span><span class="nx">path</span><span class="p">[</span><span class="mi">3</span><span class="p">];</span>
<span class="kd">var</span> <span class="nx">linkId</span> <span class="o">=</span> <span class="nx">operation</span><span class="p">.</span><span class="nx">path</span><span class="p">[</span><span class="mi">4</span><span class="p">]</span> <span class="o">||</span> <span class="nx">operation</span><span class="p">.</span><span class="nx">value</span><span class="p">;</span>
<span class="kd">var</span> <span class="nx">linkDef</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">schema</span><span class="p">.</span><span class="nx">models</span><span class="p">[</span><span class="nx">type</span><span class="p">].</span><span class="nx">links</span><span class="p">[</span><span class="nx">link</span><span class="p">];</span>
<span class="kd">var</span> <span class="nx">path</span><span class="p">;</span>
<span class="k">if </span><span class="p">(</span><span class="nx">linkDef</span><span class="p">.</span><span class="nx">type</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">hasMany</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">operation</span><span class="p">.</span><span class="nx">path</span><span class="p">.</span><span class="nf">pop</span><span class="p">();</span>
<span class="nx">path</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">/</span><span class="dl">'</span> <span class="o">+</span> <span class="nx">operation</span><span class="p">.</span><span class="nx">path</span><span class="p">.</span><span class="nf">join</span><span class="p">(</span><span class="dl">'</span><span class="s1">/</span><span class="dl">'</span><span class="p">).</span><span class="nf">replace</span><span class="p">(</span><span class="sr">/__rel/</span><span class="p">,</span> <span class="dl">'</span><span class="s1">links</span><span class="dl">'</span><span class="p">)</span> <span class="o">+</span> <span class="dl">'</span><span class="s1">/-</span><span class="dl">'</span><span class="p">;</span>
<span class="p">}</span> <span class="k">else</span> <span class="k">if </span><span class="p">(</span><span class="nx">linkDef</span><span class="p">.</span><span class="nx">type</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">hasOne</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">path</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">/</span><span class="dl">'</span> <span class="o">+</span> <span class="nx">operation</span><span class="p">.</span><span class="nx">path</span><span class="p">.</span><span class="nf">join</span><span class="p">(</span><span class="dl">'</span><span class="s1">/</span><span class="dl">'</span><span class="p">).</span><span class="nf">replace</span><span class="p">(</span><span class="sr">/__rel/</span><span class="p">,</span> <span class="dl">'</span><span class="s1">links</span><span class="dl">'</span><span class="p">);</span>
<span class="p">}</span>
<span class="kd">var</span> <span class="nx">remoteOp</span> <span class="o">=</span> <span class="p">{</span> <span class="na">path</span><span class="p">:</span> <span class="nx">path</span><span class="p">,</span> <span class="na">op</span><span class="p">:</span> <span class="dl">'</span><span class="s1">add</span><span class="dl">'</span><span class="p">,</span> <span class="na">value</span><span class="p">:</span> <span class="nx">linkId</span> <span class="p">};</span>
<span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nf">_remotePatch</span><span class="p">(</span><span class="nx">type</span><span class="p">,</span> <span class="nx">id</span><span class="p">,</span> <span class="nx">remoteOp</span><span class="p">);</span>
<span class="p">},</span>
<span class="na">_transformRemoveLink</span><span class="p">:</span> <span class="nf">function </span><span class="p">(</span><span class="nx">operation</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">type</span> <span class="o">=</span> <span class="nx">operation</span><span class="p">.</span><span class="nx">path</span><span class="p">[</span><span class="mi">0</span><span class="p">];</span>
<span class="nx">operation</span><span class="p">.</span><span class="nx">path</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">=</span> <span class="nf">pluralize</span><span class="p">(</span><span class="nx">type</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">id</span> <span class="o">=</span> <span class="nx">operation</span><span class="p">.</span><span class="nx">path</span><span class="p">[</span><span class="mi">1</span><span class="p">];</span>
<span class="kd">var</span> <span class="nx">path</span> <span class="o">=</span> <span class="dl">'</span><span class="s1">/</span><span class="dl">'</span> <span class="o">+</span> <span class="nx">operation</span><span class="p">.</span><span class="nx">path</span><span class="p">.</span><span class="nf">join</span><span class="p">(</span><span class="dl">'</span><span class="s1">/</span><span class="dl">'</span><span class="p">).</span><span class="nf">replace</span><span class="p">(</span><span class="sr">/__rel/</span><span class="p">,</span> <span class="dl">'</span><span class="s1">links</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">remoteOp</span> <span class="o">=</span> <span class="p">{</span> <span class="na">op</span><span class="p">:</span> <span class="dl">'</span><span class="s1">remove</span><span class="dl">'</span><span class="p">,</span> <span class="na">path</span><span class="p">:</span> <span class="nx">path</span> <span class="p">};</span>
<span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nf">_remotePatch</span><span class="p">(</span><span class="nx">type</span><span class="p">,</span> <span class="nx">id</span><span class="p">,</span> <span class="nx">remoteOp</span><span class="p">);</span>
<span class="p">},</span>
<span class="na">_transformReplaceLink</span><span class="p">:</span> <span class="nf">function </span><span class="p">(</span><span class="nx">operation</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nf">error</span><span class="p">(</span><span class="dl">'</span><span class="s1">TODO, SocketSource#_transformReplaceLink not supported yet</span><span class="dl">'</span><span class="p">);</span>
<span class="p">},</span>
<span class="na">_remotePatch</span><span class="p">:</span> <span class="nf">function </span><span class="p">(</span><span class="nx">type</span><span class="p">,</span> <span class="nx">id</span><span class="p">,</span> <span class="nx">remoteOp</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">records</span><span class="p">;</span>
<span class="c1">// handle promise resolution serially, pass off return to next then handler</span>
<span class="k">return</span> <span class="k">new</span> <span class="nx">Orbit</span><span class="p">.</span><span class="nc">Promise</span><span class="p">(</span><span class="kd">function</span> <span class="nf">doPatch</span><span class="p">(</span><span class="nx">resolve</span><span class="p">,</span> <span class="nx">reject</span><span class="p">)</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nx">_socket</span><span class="p">.</span><span class="nf">emit</span><span class="p">(</span><span class="dl">'</span><span class="s1">patch</span><span class="dl">'</span><span class="p">,</span> <span class="nx">JSON</span><span class="p">.</span><span class="nf">stringify</span><span class="p">(</span><span class="nx">remoteOp</span><span class="p">),</span> <span class="kd">function</span> <span class="nf">didPatch</span><span class="p">(</span><span class="nx">raw</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if </span><span class="p">(</span><span class="nx">raw</span> <span class="o">&&</span> <span class="nx">raw</span><span class="p">.</span><span class="nx">errors</span><span class="p">)</span> <span class="p">{</span>
<span class="nf">reject</span><span class="p">(</span><span class="nx">raw</span><span class="p">.</span><span class="nx">errors</span><span class="p">);</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="nf">resolve</span><span class="p">(</span><span class="nx">raw</span><span class="p">);</span> <span class="c1">// doesn't matter what raw is, socket called back w/o errors</span>
<span class="p">}</span>
<span class="p">});</span>
<span class="p">}.</span><span class="nf">bind</span><span class="p">(</span><span class="k">this</span><span class="p">))</span>
<span class="p">.</span><span class="nf">then</span><span class="p">(</span><span class="kd">function</span> <span class="nf">doProcess</span><span class="p">(</span><span class="nx">raw</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if </span><span class="p">(</span><span class="nx">raw</span> <span class="o">&&</span> <span class="nb">Array</span><span class="p">.</span><span class="nf">isArray</span><span class="p">(</span><span class="nx">raw</span><span class="p">))</span> <span class="p">{</span>
<span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nf">deserialize</span><span class="p">(</span><span class="nx">type</span><span class="p">,</span> <span class="nx">id</span><span class="p">,</span> <span class="nx">raw</span><span class="p">[</span><span class="mi">0</span><span class="p">]);</span>
<span class="p">}</span>
<span class="k">return</span> <span class="kc">null</span><span class="p">;</span>
<span class="p">}.</span><span class="nf">bind</span><span class="p">(</span><span class="k">this</span><span class="p">))</span>
<span class="p">.</span><span class="nf">then</span><span class="p">(</span><span class="nf">function </span><span class="p">(</span><span class="nx">data</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">records</span> <span class="o">=</span> <span class="nx">data</span><span class="p">;</span>
<span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nf">settleTransforms</span><span class="p">();</span>
<span class="p">}.</span><span class="nf">bind</span><span class="p">(</span><span class="k">this</span><span class="p">))</span>
<span class="p">.</span><span class="nf">then</span><span class="p">(</span><span class="nf">function </span><span class="p">()</span> <span class="p">{</span>
<span class="c1">// finally send back the records</span>
<span class="k">return</span> <span class="nx">records</span><span class="p">;</span>
<span class="p">})</span>
<span class="p">.</span><span class="k">catch</span><span class="p">(</span><span class="kd">function</span> <span class="nf">onError</span><span class="p">(</span><span class="nx">error</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nf">error</span><span class="p">(</span><span class="nx">error</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">e</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">SocketSource#_remotePatch Error w/ op: %@, path: %@</span><span class="dl">"</span><span class="p">;</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nc">Error</span><span class="p">(</span><span class="nx">e</span><span class="p">.</span><span class="nf">fmt</span><span class="p">(</span><span class="nx">remoteOp</span><span class="p">.</span><span class="nx">op</span><span class="p">,</span> <span class="nx">remoteOp</span><span class="p">.</span><span class="nx">path</span><span class="p">));</span>
<span class="p">});</span>
<span class="p">}</span>
<span class="p">});</span>
<span class="c1">// TODO use Ember.Inflector https://github.com/stefanpenner/ember-inflector.git</span>
<span class="kd">var</span> <span class="nx">pluralize</span> <span class="o">=</span> <span class="nf">function </span><span class="p">(</span><span class="nx">name</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">name</span> <span class="o">+</span> <span class="dl">'</span><span class="s1">s</span><span class="dl">'</span><span class="p">;</span>
<span class="p">};</span>
<span class="c1">// borrowed from 'orbit/lib/objects'</span>
<span class="kd">var</span> <span class="nx">isObject</span> <span class="o">=</span> <span class="kd">function</span><span class="p">(</span><span class="nx">obj</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">obj</span> <span class="o">!==</span> <span class="kc">null</span> <span class="o">&&</span> <span class="k">typeof</span> <span class="nx">obj</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">object</span><span class="dl">'</span><span class="p">;</span>
<span class="p">};</span>
<span class="k">export</span> <span class="k">default</span> <span class="nx">SocketSource</span><span class="p">;</span>
</code></pre>
<h3>Stores</h3>
<p>"Stores extend sources and are also repositories for models. All of the data in a store is maintained in its internal source."</p>
<p>Below are a few examples of how I use the application store in a Route:</p>
<p>Custom query for posts</p>
<pre class="highlight"><code><span class="nx">model</span><span class="p">:</span> <span class="nf">function </span><span class="p">()</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">query</span> <span class="o">=</span> <span class="p">{</span> <span class="na">offset</span><span class="p">:</span> <span class="k">this</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="dl">'</span><span class="s1">offset</span><span class="dl">'</span><span class="p">),</span> <span class="na">limit</span><span class="p">:</span> <span class="k">this</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="dl">'</span><span class="s1">limit</span><span class="dl">'</span><span class="p">)</span> <span class="p">};</span>
<span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">store</span><span class="p">.</span><span class="nf">find</span><span class="p">(</span><span class="dl">'</span><span class="s1">post</span><span class="dl">'</span><span class="p">,</span> <span class="nx">query</span><span class="p">);</span>
<span class="p">},</span>
</code></pre>
<p>List of posts</p>
<pre class="highlight"><code><span class="nx">model</span><span class="p">:</span> <span class="nf">function </span><span class="p">()</span> <span class="p">{</span>
<span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">store</span><span class="p">.</span><span class="nf">find</span><span class="p">(</span><span class="dl">'</span><span class="s1">post</span><span class="dl">'</span><span class="p">);</span>
<span class="p">},</span>
</code></pre>
<p>A specific post by id</p>
<pre class="highlight"><code><span class="nx">model</span><span class="p">:</span> <span class="nf">function </span><span class="p">(</span><span class="nx">params</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">store</span><span class="p">.</span><span class="nf">find</span><span class="p">(</span><span class="dl">'</span><span class="s1">post</span><span class="dl">'</span><span class="p">,</span> <span class="nx">params</span><span class="p">.</span><span class="nx">edit_id</span><span class="p">);</span>
<span class="p">},</span>
</code></pre>
<p>A post route that can find a post from the store's memory or ask the secondary source (adapter) for the resource</p>
<pre class="highlight"><code><span class="nx">model</span><span class="p">:</span> <span class="nf">function </span><span class="p">(</span><span class="nx">params</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="k">new</span> <span class="nx">Ember</span><span class="p">.</span><span class="nx">RSVP</span><span class="p">.</span><span class="nc">Promise</span><span class="p">(</span><span class="nf">function </span><span class="p">(</span><span class="nx">resolve</span><span class="p">,</span> <span class="nx">reject</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">found</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">store</span><span class="p">.</span><span class="nf">filter</span><span class="p">(</span><span class="dl">'</span><span class="s1">post</span><span class="dl">'</span><span class="p">,</span> <span class="nf">function </span><span class="p">(</span><span class="nx">post</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">post</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="dl">'</span><span class="s1">slug</span><span class="dl">'</span><span class="p">)</span> <span class="o">===</span> <span class="nx">params</span><span class="p">.</span><span class="nx">post_slug</span><span class="p">;</span>
<span class="p">});</span>
<span class="k">if </span><span class="p">(</span><span class="nx">found</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="dl">'</span><span class="s1">length</span><span class="dl">'</span><span class="p">)</span> <span class="o">></span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
<span class="nf">resolve</span><span class="p">(</span><span class="nx">found</span><span class="p">[</span><span class="mi">0</span><span class="p">]);</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nx">store</span><span class="p">.</span><span class="nf">find</span><span class="p">(</span><span class="dl">'</span><span class="s1">post</span><span class="dl">'</span><span class="p">,</span> <span class="nx">params</span><span class="p">.</span><span class="nx">post_slug</span><span class="p">).</span><span class="nf">then</span><span class="p">(</span>
<span class="nf">function </span><span class="p">(</span><span class="nx">post</span><span class="p">)</span> <span class="p">{</span>
<span class="nf">resolve</span><span class="p">(</span><span class="nx">post</span><span class="p">);</span>
<span class="p">},</span>
<span class="nf">function </span><span class="p">(</span><span class="nx">error</span><span class="p">)</span> <span class="p">{</span>
<span class="nf">reject</span><span class="p">(</span><span class="nx">error</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">);</span>
<span class="p">}</span>
<span class="p">}.</span><span class="nf">bind</span><span class="p">(</span><span class="k">this</span><span class="p">));</span>
<span class="p">},</span>
</code></pre>
<h4>Push Support</h4>
<p>Routes can use a mixin for push support for real-time update to connected clients.</p>
<p><a href="proxy.php?url=https://github.com/pixelhandler/blog/blob/master/client/app/mixins/push-support.js" target="_blank" rel="noopener noreferrer">mixins/push-support.js</a></p>
<pre class="highlight"><code><span class="k">import</span> <span class="nx">Ember</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">ember</span><span class="dl">'</span><span class="p">;</span>
<span class="k">export</span> <span class="k">default</span> <span class="nx">Ember</span><span class="p">.</span><span class="nx">Mixin</span><span class="p">.</span><span class="nf">create</span><span class="p">({</span>
<span class="na">beforeModel</span><span class="p">:</span> <span class="nf">function </span><span class="p">()</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nf">socketSanityCheck</span><span class="p">();</span>
<span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nf">_super</span><span class="p">();</span>
<span class="p">},</span>
<span class="na">socketSanityCheck</span><span class="p">:</span> <span class="nf">function </span><span class="p">()</span> <span class="p">{</span>
<span class="c1">// Sanity check, is socket working? check output browser console.</span>
<span class="kd">var</span> <span class="nx">socket</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">socket</span><span class="p">;</span>
<span class="nx">socket</span><span class="p">.</span><span class="nf">on</span><span class="p">(</span><span class="dl">'</span><span class="s1">hello</span><span class="dl">'</span><span class="p">,</span> <span class="nf">function </span><span class="p">(</span><span class="nx">data</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="nx">data</span><span class="p">);</span>
<span class="nx">socket</span><span class="p">.</span><span class="nf">emit</span><span class="p">(</span><span class="dl">'</span><span class="s1">talk-to-me</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">I like talking.</span><span class="dl">'</span><span class="p">,</span> <span class="nf">function </span><span class="p">(</span><span class="nx">msg</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nf">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">back talk</span><span class="dl">'</span><span class="p">,</span> <span class="nx">msg</span><span class="p">);</span>
<span class="p">});</span>
<span class="p">});</span>
<span class="p">},</span>
<span class="c1">// Template methods...</span>
<span class="na">onDidPatch</span><span class="p">:</span> <span class="nx">Ember</span><span class="p">.</span><span class="nx">required</span><span class="p">,</span>
<span class="na">patchRecord</span><span class="p">:</span> <span class="nf">function </span><span class="p">(</span><span class="nx">operation</span><span class="p">)</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nf">_patchRecord</span><span class="p">(</span><span class="nx">operation</span><span class="p">);</span>
<span class="p">},</span>
<span class="na">addLink</span><span class="p">:</span> <span class="nx">Ember</span><span class="p">.</span><span class="nx">K</span><span class="p">,</span>
<span class="na">replaceLink</span><span class="p">:</span> <span class="nx">Ember</span><span class="p">.</span><span class="nx">K</span><span class="p">,</span>
<span class="na">removeLink</span><span class="p">:</span> <span class="nx">Ember</span><span class="p">.</span><span class="nx">K</span><span class="p">,</span>
<span class="na">addRecord</span><span class="p">:</span> <span class="nx">Ember</span><span class="p">.</span><span class="nx">K</span><span class="p">,</span>
<span class="na">updateAttribute</span><span class="p">:</span> <span class="nx">Ember</span><span class="p">.</span><span class="nx">K</span><span class="p">,</span>
<span class="na">deleteRecord</span><span class="p">:</span> <span class="nx">Ember</span><span class="p">.</span><span class="nx">K</span><span class="p">,</span>
<span class="c1">// Use in template methods...</span>
<span class="na">_patchRecord</span><span class="p">:</span> <span class="nf">function </span><span class="p">(</span><span class="nx">operation</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">operation</span> <span class="o">=</span> <span class="p">(</span><span class="k">typeof</span> <span class="nx">operation</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">string</span><span class="dl">'</span><span class="p">)</span> <span class="p">?</span> <span class="nx">JSON</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="nx">operation</span><span class="p">)</span> <span class="p">:</span> <span class="nx">operation</span><span class="p">;</span>
<span class="k">if </span><span class="p">(</span><span class="o">!</span><span class="nx">operation</span><span class="p">.</span><span class="nx">op</span> <span class="o">||</span> <span class="o">!</span><span class="nx">operation</span><span class="p">.</span><span class="nx">path</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nf">error</span><span class="p">(</span><span class="dl">'</span><span class="s1">Push error! Invalid patch operation.</span><span class="dl">'</span><span class="p">);</span>
<span class="k">return</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">if </span><span class="p">(</span><span class="nx">operation</span><span class="p">.</span><span class="nx">path</span><span class="p">.</span><span class="nf">match</span><span class="p">(</span><span class="dl">'</span><span class="s1">/links/</span><span class="dl">'</span><span class="p">)</span> <span class="o">!==</span> <span class="kc">null</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if </span><span class="p">(</span><span class="nx">operation</span><span class="p">.</span><span class="nx">op</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">add</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">Ember</span><span class="p">.</span><span class="nx">run</span><span class="p">.</span><span class="nf">later</span><span class="p">(</span><span class="k">this</span><span class="p">,</span> <span class="dl">'</span><span class="s1">addLink</span><span class="dl">'</span><span class="p">,</span> <span class="nx">operation</span><span class="p">,</span> <span class="k">this</span><span class="p">.</span><span class="nx">_delay</span><span class="p">);</span>
<span class="p">}</span> <span class="k">else</span> <span class="k">if </span><span class="p">(</span><span class="nx">operation</span><span class="p">.</span><span class="nx">op</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">replace</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">Ember</span><span class="p">.</span><span class="nx">run</span><span class="p">.</span><span class="nf">next</span><span class="p">(</span><span class="k">this</span><span class="p">,</span> <span class="dl">'</span><span class="s1">replaceLink</span><span class="dl">'</span><span class="p">,</span> <span class="nx">operation</span><span class="p">);</span>
<span class="p">}</span> <span class="k">else</span> <span class="k">if </span><span class="p">(</span><span class="nx">operation</span><span class="p">.</span><span class="nx">op</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">remove</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">Ember</span><span class="p">.</span><span class="nx">run</span><span class="p">.</span><span class="nf">next</span><span class="p">(</span><span class="k">this</span><span class="p">,</span> <span class="dl">'</span><span class="s1">removeLink</span><span class="dl">'</span><span class="p">,</span> <span class="nx">operation</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="k">if </span><span class="p">(</span><span class="nx">operation</span><span class="p">.</span><span class="nx">op</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">add</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">Ember</span><span class="p">.</span><span class="nx">run</span><span class="p">.</span><span class="nf">next</span><span class="p">(</span><span class="k">this</span><span class="p">,</span> <span class="dl">'</span><span class="s1">addRecord</span><span class="dl">'</span><span class="p">,</span> <span class="nx">operation</span><span class="p">);</span>
<span class="p">}</span> <span class="k">else</span> <span class="k">if </span><span class="p">(</span><span class="nx">operation</span><span class="p">.</span><span class="nx">op</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">replace</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">Ember</span><span class="p">.</span><span class="nx">run</span><span class="p">.</span><span class="nf">next</span><span class="p">(</span><span class="k">this</span><span class="p">,</span> <span class="dl">'</span><span class="s1">updateAttribute</span><span class="dl">'</span><span class="p">,</span> <span class="nx">operation</span><span class="p">);</span>
<span class="p">}</span> <span class="k">else</span> <span class="k">if </span><span class="p">(</span><span class="nx">operation</span><span class="p">.</span><span class="nx">op</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">remove</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">Ember</span><span class="p">.</span><span class="nx">run</span><span class="p">.</span><span class="nf">next</span><span class="p">(</span><span class="k">this</span><span class="p">,</span> <span class="dl">'</span><span class="s1">deleteRecord</span><span class="dl">'</span><span class="p">,</span> <span class="nx">operation</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">},</span>
<span class="na">_addLink</span><span class="p">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">operation</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">model</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nf">_retrieveModel</span><span class="p">(</span><span class="nx">operation</span><span class="p">);</span>
<span class="k">if </span><span class="p">(</span><span class="nx">model</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">type</span> <span class="o">=</span> <span class="nx">operation</span><span class="p">.</span><span class="nx">path</span><span class="p">.</span><span class="nf">split</span><span class="p">(</span><span class="dl">'</span><span class="s1">/links/</span><span class="dl">'</span><span class="p">)[</span><span class="mi">1</span><span class="p">];</span>
<span class="kd">var</span> <span class="nx">relation</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">store</span><span class="p">.</span><span class="nf">retrieve</span><span class="p">(</span><span class="nx">type</span><span class="p">,</span> <span class="p">{</span> <span class="na">primaryId</span><span class="p">:</span> <span class="nx">operation</span><span class="p">.</span><span class="nx">value</span> <span class="p">});</span>
<span class="k">if </span><span class="p">(</span><span class="nx">relation</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">model</span><span class="p">.</span><span class="nf">addLink</span><span class="p">(</span><span class="nx">type</span><span class="p">,</span> <span class="nx">relation</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">},</span>
<span class="na">_replaceLink</span><span class="p">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">operation</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nf">error</span><span class="p">(</span><span class="dl">'</span><span class="s1">TODO replaceLink not supported yet</span><span class="dl">'</span><span class="p">,</span> <span class="nx">operation</span><span class="p">);</span>
<span class="p">},</span>
<span class="na">_removeLink</span><span class="p">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">operation</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">model</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nf">_retrieveModel</span><span class="p">(</span><span class="nx">operation</span><span class="p">);</span>
<span class="k">if </span><span class="p">(</span><span class="nx">model</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">path</span> <span class="o">=</span> <span class="nx">operation</span><span class="p">.</span><span class="nx">path</span><span class="p">.</span><span class="nf">split</span><span class="p">(</span><span class="dl">'</span><span class="s1">/links/</span><span class="dl">'</span><span class="p">)[</span><span class="mi">1</span><span class="p">].</span><span class="nf">split</span><span class="p">(</span><span class="dl">'</span><span class="s1">/</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">type</span> <span class="o">=</span> <span class="nx">path</span><span class="p">[</span><span class="mi">0</span><span class="p">];</span>
<span class="kd">var</span> <span class="nx">id</span> <span class="o">=</span> <span class="nx">path</span><span class="p">[</span><span class="mi">1</span><span class="p">];</span>
<span class="kd">var</span> <span class="nx">relation</span> <span class="o">=</span> <span class="kc">null</span><span class="p">;</span>
<span class="k">if </span><span class="p">(</span><span class="nx">id</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">relation</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">store</span><span class="p">.</span><span class="nf">retrieve</span><span class="p">(</span><span class="nx">type</span><span class="p">,</span> <span class="p">{</span> <span class="na">primaryId</span><span class="p">:</span> <span class="nx">id</span> <span class="p">});</span>
<span class="p">}</span>
<span class="nx">model</span><span class="p">.</span><span class="nf">removeLink</span><span class="p">(</span><span class="nx">type</span><span class="p">,</span> <span class="nx">relation</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">},</span>
<span class="na">_addRecord</span><span class="p">:</span> <span class="nf">function </span><span class="p">(</span><span class="nx">operation</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">type</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nf">_extractType</span><span class="p">(</span><span class="nx">operation</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">id</span> <span class="o">=</span> <span class="nx">operation</span><span class="p">.</span><span class="nx">value</span><span class="p">.</span><span class="nx">id</span><span class="p">;</span>
<span class="kd">var</span> <span class="nx">model</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">store</span><span class="p">.</span><span class="nf">retrieve</span><span class="p">(</span><span class="nx">type</span><span class="p">,</span> <span class="p">{</span> <span class="na">primaryId</span><span class="p">:</span> <span class="nx">id</span> <span class="p">});</span>
<span class="k">if </span><span class="p">(</span><span class="o">!</span><span class="nx">model</span><span class="p">)</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nx">store</span><span class="p">.</span><span class="nf">add</span><span class="p">(</span><span class="nx">type</span><span class="p">,</span> <span class="nx">operation</span><span class="p">.</span><span class="nx">value</span><span class="p">);</span>
<span class="k">this</span><span class="p">.</span><span class="nx">store</span><span class="p">.</span><span class="nf">then</span><span class="p">(</span><span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
<span class="nx">model</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">store</span><span class="p">.</span><span class="nf">retrieve</span><span class="p">(</span><span class="nx">type</span><span class="p">,</span> <span class="p">{</span> <span class="na">primaryId</span><span class="p">:</span> <span class="nx">id</span> <span class="p">});</span>
<span class="kd">var</span> <span class="nx">name</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="dl">'</span><span class="s1">routeName</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">collection</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nf">modelFor</span><span class="p">(</span><span class="nx">name</span><span class="p">);</span>
<span class="k">if </span><span class="p">(</span><span class="nx">collection</span> <span class="o">&&</span> <span class="o">!</span><span class="nx">collection</span><span class="p">.</span><span class="nf">contains</span><span class="p">(</span><span class="nx">model</span><span class="p">))</span> <span class="p">{</span>
<span class="nx">collection</span><span class="p">.</span><span class="nf">insertAt</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="nx">model</span><span class="p">);</span>
<span class="k">this</span><span class="p">.</span><span class="nf">controllerFor</span><span class="p">(</span><span class="nx">name</span><span class="p">).</span><span class="nf">set</span><span class="p">(</span><span class="dl">'</span><span class="s1">model</span><span class="dl">'</span><span class="p">,</span> <span class="nx">collection</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}.</span><span class="nf">bind</span><span class="p">(</span><span class="k">this</span><span class="p">));</span>
<span class="p">}</span>
<span class="p">},</span>
<span class="na">_updateAttribute</span><span class="p">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">operation</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">type</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nf">_extractType</span><span class="p">(</span><span class="nx">operation</span><span class="p">);</span>
<span class="k">if </span><span class="p">(</span><span class="o">!</span><span class="nx">type</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span><span class="p">;</span>
<span class="p">}</span>
<span class="kd">var</span> <span class="nx">typeKey</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">store</span><span class="p">.</span><span class="nx">schema</span><span class="p">.</span><span class="nx">_schema</span><span class="p">.</span><span class="nf">pluralize</span><span class="p">(</span><span class="nx">type</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">path</span> <span class="o">=</span> <span class="nx">operation</span><span class="p">.</span><span class="nx">path</span><span class="p">.</span><span class="nf">split</span><span class="p">(</span><span class="dl">'</span><span class="s1">/</span><span class="dl">'</span> <span class="o">+</span> <span class="nx">typeKey</span> <span class="o">+</span> <span class="dl">'</span><span class="s1">/</span><span class="dl">'</span><span class="p">)[</span><span class="mi">1</span><span class="p">];</span>
<span class="kd">var</span> <span class="nx">id</span><span class="p">,</span> <span class="nx">attribute</span><span class="p">;</span>
<span class="k">if </span><span class="p">(</span><span class="nx">path</span><span class="p">.</span><span class="nf">indexOf</span><span class="p">(</span><span class="dl">'</span><span class="s1">/</span><span class="dl">'</span><span class="p">)</span> <span class="o">!==</span> <span class="o">-</span><span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">path</span> <span class="o">=</span> <span class="nx">path</span><span class="p">.</span><span class="nf">split</span><span class="p">(</span><span class="dl">'</span><span class="s1">/</span><span class="dl">'</span><span class="p">);</span>
<span class="nx">id</span> <span class="o">=</span> <span class="nx">path</span><span class="p">[</span><span class="mi">0</span><span class="p">];</span>
<span class="nx">attribute</span> <span class="o">=</span> <span class="nx">path</span><span class="p">[</span><span class="mi">1</span><span class="p">];</span>
<span class="p">}</span>
<span class="kd">var</span> <span class="nx">model</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">store</span><span class="p">.</span><span class="nf">retrieve</span><span class="p">(</span><span class="nx">type</span><span class="p">,</span> <span class="p">{</span><span class="na">primaryId</span><span class="p">:</span> <span class="nx">id</span><span class="p">});</span>
<span class="k">if </span><span class="p">(</span><span class="nx">model</span> <span class="o">&&</span> <span class="nx">attribute</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">model</span><span class="p">.</span><span class="nf">set</span><span class="p">(</span><span class="nx">attribute</span><span class="p">,</span> <span class="nx">operation</span><span class="p">.</span><span class="nx">value</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">},</span>
<span class="na">_deleteRecord</span><span class="p">:</span> <span class="nf">function </span><span class="p">(</span><span class="nx">operation</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">model</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nf">_retrieveModel</span><span class="p">(</span><span class="nx">operation</span><span class="p">);</span>
<span class="k">if </span><span class="p">(</span><span class="nx">model</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">name</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="dl">'</span><span class="s1">routeName</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">collection</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nf">modelFor</span><span class="p">(</span><span class="nx">name</span><span class="p">);</span>
<span class="k">if </span><span class="p">(</span><span class="nx">collection</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">collection</span><span class="p">.</span><span class="nf">removeObject</span><span class="p">(</span><span class="nx">model</span><span class="p">);</span>
<span class="p">}</span>
<span class="kd">var</span> <span class="nx">controller</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nf">controllerFor</span><span class="p">(</span><span class="nx">name</span><span class="p">);</span>
<span class="k">if </span><span class="p">(</span><span class="nx">controller</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">controller</span><span class="p">.</span><span class="nf">removeObject</span><span class="p">(</span><span class="nx">model</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">if </span><span class="p">(</span><span class="nx">model</span><span class="p">.</span><span class="kd">constructor</span><span class="p">.</span><span class="nx">typeKey</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">type</span> <span class="o">=</span> <span class="nx">model</span><span class="p">.</span><span class="kd">constructor</span><span class="p">.</span><span class="nx">typeKey</span><span class="p">;</span>
<span class="kd">var</span> <span class="nx">id</span> <span class="o">=</span> <span class="nx">model</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="dl">'</span><span class="s1">primaryId</span><span class="dl">'</span><span class="p">);</span>
<span class="nx">Ember</span><span class="p">.</span><span class="nx">run</span><span class="p">.</span><span class="nf">later</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">store</span><span class="p">,</span> <span class="dl">'</span><span class="s1">remove</span><span class="dl">'</span><span class="p">,</span> <span class="nx">type</span><span class="p">,</span> <span class="nx">id</span><span class="p">,</span> <span class="k">this</span><span class="p">.</span><span class="nx">_delay</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">},</span>
<span class="na">_extractType</span><span class="p">:</span> <span class="nf">function </span><span class="p">(</span><span class="nx">operation</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">path</span> <span class="o">=</span> <span class="nx">operation</span><span class="p">.</span><span class="nx">path</span><span class="p">.</span><span class="nf">split</span><span class="p">(</span><span class="dl">'</span><span class="s1">/</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">type</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">store</span><span class="p">.</span><span class="nx">schema</span><span class="p">.</span><span class="nx">_schema</span><span class="p">.</span><span class="nf">singularize</span><span class="p">(</span><span class="nx">path</span><span class="p">[</span><span class="mi">1</span><span class="p">]);</span>
<span class="k">if </span><span class="p">(</span><span class="o">!</span><span class="k">this</span><span class="p">.</span><span class="nx">store</span><span class="p">.</span><span class="nx">schema</span><span class="p">.</span><span class="nx">_schema</span><span class="p">.</span><span class="nx">models</span><span class="p">[</span><span class="nx">type</span><span class="p">])</span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nf">error</span><span class="p">(</span><span class="dl">'</span><span class="s1">Cannot extract type</span><span class="dl">'</span><span class="p">,</span> <span class="nx">path</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">return</span> <span class="nx">type</span><span class="p">;</span>
<span class="p">},</span>
<span class="na">_retrieveModel</span><span class="p">:</span> <span class="nf">function </span><span class="p">(</span><span class="nx">operation</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">path</span> <span class="o">=</span> <span class="nx">operation</span><span class="p">.</span><span class="nx">path</span><span class="p">.</span><span class="nf">split</span><span class="p">(</span><span class="dl">'</span><span class="s1">/</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">type</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nx">store</span><span class="p">.</span><span class="nx">schema</span><span class="p">.</span><span class="nx">_schema</span><span class="p">.</span><span class="nf">singularize</span><span class="p">(</span><span class="nx">path</span><span class="p">[</span><span class="mi">1</span><span class="p">]);</span>
<span class="kd">var</span> <span class="nx">id</span> <span class="o">=</span> <span class="nx">path</span><span class="p">[</span><span class="mi">2</span><span class="p">];</span>
<span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">store</span><span class="p">.</span><span class="nf">retrieve</span><span class="p">(</span><span class="nx">type</span><span class="p">,</span> <span class="p">{</span> <span class="na">primaryId</span><span class="p">:</span> <span class="nx">id</span> <span class="p">});</span>
<span class="p">},</span>
<span class="na">_delay</span><span class="p">:</span> <span class="mi">1000</span>
<span class="p">});</span>
</code></pre>
<h3>Routes using the push support mixin</h3>
<p><a href="proxy.php?url=https://github.com/pixelhandler/blog/blob/master/client/app/routes/application.js" target="_blank" rel="noopener noreferrer">routes/application.js</a></p>
<pre class="highlight"><code><span class="k">import</span> <span class="nx">Ember</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">ember</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="nx">PushSupport</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../mixins/push-support</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="nx">config</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../config/environment</span><span class="dl">'</span><span class="p">;</span>
<span class="kd">var</span> <span class="nx">ApplicationRoute</span> <span class="o">=</span> <span class="nx">Ember</span><span class="p">.</span><span class="nx">Route</span><span class="p">.</span><span class="nf">extend</span><span class="p">(</span><span class="nx">PushSupport</span><span class="p">,</span> <span class="p">{</span>
<span class="na">model</span><span class="p">:</span> <span class="nf">function </span><span class="p">()</span> <span class="p">{</span>
<span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">store</span><span class="p">.</span><span class="nf">find</span><span class="p">(</span><span class="dl">'</span><span class="s1">post</span><span class="dl">'</span><span class="p">);</span>
<span class="p">},</span>
<span class="cm">/* some code not included here */</span>
<span class="c1">// Push support...</span>
<span class="na">onDidPatch</span><span class="p">:</span> <span class="nf">function </span><span class="p">()</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nx">socket</span><span class="p">.</span><span class="nf">on</span><span class="p">(</span><span class="dl">'</span><span class="s1">didPatch</span><span class="dl">'</span><span class="p">,</span> <span class="k">this</span><span class="p">.</span><span class="nx">patchRecord</span><span class="p">.</span><span class="nf">bind</span><span class="p">(</span><span class="k">this</span><span class="p">));</span>
<span class="p">}.</span><span class="nf">on</span><span class="p">(</span><span class="dl">'</span><span class="s1">init</span><span class="dl">'</span><span class="p">),</span>
<span class="na">addLink</span><span class="p">:</span> <span class="nf">function </span><span class="p">(</span><span class="nx">operation</span><span class="p">)</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nf">_addLink</span><span class="p">(</span><span class="nx">operation</span><span class="p">);</span>
<span class="p">},</span>
<span class="na">removeLink</span><span class="p">:</span> <span class="nf">function </span><span class="p">(</span><span class="nx">operation</span><span class="p">)</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nf">_removeLink</span><span class="p">(</span><span class="nx">operation</span><span class="p">);</span>
<span class="p">},</span>
<span class="na">addRecord</span><span class="p">:</span> <span class="nf">function </span><span class="p">(</span><span class="nx">operation</span><span class="p">)</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nf">_addRecord</span><span class="p">(</span><span class="nx">operation</span><span class="p">);</span>
<span class="p">},</span>
<span class="na">updateAttribute</span><span class="p">:</span> <span class="nf">function </span><span class="p">(</span><span class="nx">operation</span><span class="p">)</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nf">_updateAttribute</span><span class="p">(</span><span class="nx">operation</span><span class="p">);</span>
<span class="p">},</span>
<span class="na">deleteRecord</span><span class="p">:</span> <span class="nf">function </span><span class="p">(</span><span class="nx">operation</span><span class="p">)</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nf">_deleteRecord</span><span class="p">(</span><span class="nx">operation</span><span class="p">);</span>
<span class="p">},</span>
<span class="cm">/* some code not included here */</span>
<span class="p">});</span>
<span class="kd">function</span> <span class="nf">lookupSocket</span><span class="p">(</span><span class="nx">container</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if </span><span class="p">(</span><span class="o">!</span><span class="nb">window</span><span class="p">.</span><span class="nx">WebSocket</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="kc">false</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">return</span> <span class="nx">container</span><span class="p">.</span><span class="nf">lookup</span><span class="p">(</span><span class="dl">'</span><span class="s1">socket:main</span><span class="dl">'</span><span class="p">);</span>
<span class="p">}</span>
<span class="cm">/* some code not included here */</span>
<span class="k">export</span> <span class="k">default</span> <span class="nx">ApplicationRoute</span><span class="p">;</span>
</code></pre>
<p><a href="proxy.php?url=https://github.com/pixelhandler/blog/blob/master/client/app/routes/index.js" target="_blank" rel="noopener noreferrer">routes/index.js</a></p>
<pre class="highlight"><code><span class="k">import</span> <span class="nx">Ember</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">ember</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="nx">RecordChunksMixin</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../mixins/record-chunks</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="nx">ResetScroll</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../mixins/reset-scroll</span><span class="dl">'</span><span class="p">;</span>
<span class="k">import</span> <span class="nx">PushSupport</span> <span class="k">from</span> <span class="dl">'</span><span class="s1">../mixins/push-support</span><span class="dl">'</span><span class="p">;</span>
<span class="k">export</span> <span class="k">default</span> <span class="nx">Ember</span><span class="p">.</span><span class="nx">Route</span><span class="p">.</span><span class="nf">extend</span><span class="p">(</span><span class="nx">ResetScroll</span><span class="p">,</span> <span class="nx">RecordChunksMixin</span><span class="p">,</span> <span class="nx">PushSupport</span><span class="p">,</span> <span class="p">{</span>
<span class="cm">/* some code not included here */</span>
<span class="na">model</span><span class="p">:</span> <span class="nf">function </span><span class="p">()</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">posts</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nf">modelFor</span><span class="p">(</span><span class="dl">'</span><span class="s1">application</span><span class="dl">'</span><span class="p">);</span>
<span class="k">if </span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="dl">'</span><span class="s1">offset</span><span class="dl">'</span><span class="p">)</span> <span class="o"><</span> <span class="nx">posts</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="dl">'</span><span class="s1">length</span><span class="dl">'</span><span class="p">))</span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">posts</span><span class="p">;</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">query</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nf">buildQuery</span><span class="p">();</span>
<span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">store</span><span class="p">.</span><span class="nf">find</span><span class="p">(</span><span class="dl">'</span><span class="s1">post</span><span class="dl">'</span><span class="p">,</span> <span class="nx">query</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">},</span>
<span class="c1">// Push support...</span>
<span class="na">onDidPatch</span><span class="p">:</span> <span class="nf">function </span><span class="p">()</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nx">socket</span><span class="p">.</span><span class="nf">on</span><span class="p">(</span><span class="dl">'</span><span class="s1">didPatch</span><span class="dl">'</span><span class="p">,</span> <span class="k">this</span><span class="p">.</span><span class="nx">patchRecord</span><span class="p">.</span><span class="nf">bind</span><span class="p">(</span><span class="k">this</span><span class="p">));</span>
<span class="p">}.</span><span class="nf">on</span><span class="p">(</span><span class="dl">'</span><span class="s1">init</span><span class="dl">'</span><span class="p">),</span>
<span class="na">addRecord</span><span class="p">:</span> <span class="nf">function </span><span class="p">(</span><span class="nx">operation</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if </span><span class="p">(</span><span class="nx">operation</span><span class="p">.</span><span class="nx">path</span><span class="p">.</span><span class="nf">split</span><span class="p">(</span><span class="dl">'</span><span class="s1">/</span><span class="dl">'</span><span class="p">)[</span><span class="mi">1</span><span class="p">]</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">posts</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">posts</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nf">model</span><span class="p">();</span>
<span class="kd">var</span> <span class="nx">controller</span> <span class="o">=</span> <span class="k">this</span><span class="p">.</span><span class="nf">controllerFor</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nf">get</span><span class="p">(</span><span class="dl">'</span><span class="s1">routeName</span><span class="dl">'</span><span class="p">));</span>
<span class="k">if </span><span class="p">(</span><span class="k">typeof</span> <span class="nx">posts</span><span class="p">.</span><span class="nx">then</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">function</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">posts</span><span class="p">.</span><span class="nf">then</span><span class="p">(</span><span class="nf">function </span><span class="p">(</span><span class="nx">_posts</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">controller</span><span class="p">.</span><span class="nf">set</span><span class="p">(</span><span class="dl">'</span><span class="s1">model</span><span class="dl">'</span><span class="p">,</span> <span class="nx">_posts</span><span class="p">);</span>
<span class="p">});</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="nx">controller</span><span class="p">.</span><span class="nf">set</span><span class="p">(</span><span class="dl">'</span><span class="s1">model</span><span class="dl">'</span><span class="p">,</span> <span class="nx">posts</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">},</span>
<span class="na">deleteRecord</span><span class="p">:</span> <span class="nf">function </span><span class="p">(</span><span class="nx">operation</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if </span><span class="p">(</span><span class="nx">operation</span><span class="p">.</span><span class="nx">path</span><span class="p">.</span><span class="nf">split</span><span class="p">(</span><span class="dl">'</span><span class="s1">/</span><span class="dl">'</span><span class="p">)[</span><span class="mi">1</span><span class="p">]</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">posts</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nf">_deleteRecord</span><span class="p">(</span><span class="nx">operation</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="cm">/* some code not included here */</span>
<span class="p">});</span>
</code></pre>
<h2><a href="proxy.php?url=http://socket.io/" target="_blank" rel="noopener noreferrer">Socket.IO</a></h2>
<p>The push support is provided by an adapter on the server which uses <a href="proxy.php?url=http://socket.io/" target="_blank" rel="noopener noreferrer">Socket.IO</a> and interacts directly with a document storage database.</p>
<p><a href="proxy.php?url=https://github.com/pixelhandler/blog/blob/master/server/lib/socket_adapter.js" target="_blank" rel="noopener noreferrer">server/lib/socket_adapter.js</a></p>
<pre class="highlight"><code><span class="cm">/**
@module app
@submodule socket_adapter
db adapter using Socket.io
**/</span>
<span class="kd">var</span> <span class="nx">db</span> <span class="o">=</span> <span class="nf">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">./rethinkdb_adapter</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">debug</span> <span class="o">=</span> <span class="nf">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">debug</span><span class="dl">'</span><span class="p">)(</span><span class="dl">'</span><span class="s1">socket_adapter</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">config</span> <span class="o">=</span> <span class="nf">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">../config</span><span class="dl">'</span><span class="p">)();</span>
<span class="cm">/**
Exports setup function
@param {Object} express server
@return {Object} `io` socket.io instance
**/</span>
<span class="nx">module</span><span class="p">.</span><span class="nx">exports</span> <span class="o">=</span> <span class="kd">function</span><span class="p">(</span><span class="nx">server</span><span class="p">,</span> <span class="nx">sessionMiddleware</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">// options: https://github.com/Automattic/engine.io#methods-1</span>
<span class="kd">var</span> <span class="nx">options</span> <span class="o">=</span> <span class="p">{</span>
<span class="dl">'</span><span class="s1">transports</span><span class="dl">'</span><span class="p">:</span> <span class="p">[</span><span class="dl">'</span><span class="s1">websocket</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">polling</span><span class="dl">'</span><span class="p">],</span>
<span class="dl">'</span><span class="s1">cookie</span><span class="dl">'</span><span class="p">:</span> <span class="dl">'</span><span class="s1">connect.sid</span><span class="dl">'</span>
<span class="p">};</span>
<span class="kd">var</span> <span class="nx">io</span> <span class="o">=</span> <span class="nf">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">socket.io</span><span class="dl">'</span><span class="p">)(</span><span class="nx">server</span><span class="p">,</span> <span class="nx">options</span><span class="p">);</span>
<span class="nx">io</span><span class="p">.</span><span class="nf">use</span><span class="p">(</span><span class="kd">function</span><span class="p">(</span><span class="nx">socket</span><span class="p">,</span> <span class="nx">next</span><span class="p">)</span> <span class="p">{</span>
<span class="nf">sessionMiddleware</span><span class="p">(</span><span class="nx">socket</span><span class="p">.</span><span class="nx">request</span><span class="p">,</span> <span class="nx">socket</span><span class="p">.</span><span class="nx">request</span><span class="p">.</span><span class="nx">res</span><span class="p">,</span> <span class="nx">next</span><span class="p">);</span>
<span class="p">});</span>
<span class="nx">io</span><span class="p">.</span><span class="nf">on</span><span class="p">(</span><span class="dl">'</span><span class="s1">connection</span><span class="dl">'</span><span class="p">,</span> <span class="nf">function </span><span class="p">(</span><span class="nx">socket</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">// Simple sanity check for client to confirm socket is working</span>
<span class="nx">socket</span><span class="p">.</span><span class="nf">emit</span><span class="p">(</span><span class="dl">'</span><span class="s1">hello</span><span class="dl">'</span><span class="p">,</span> <span class="p">{</span> <span class="na">hello</span><span class="p">:</span> <span class="dl">'</span><span class="s1">world</span><span class="dl">'</span> <span class="p">});</span>
<span class="nx">socket</span><span class="p">.</span><span class="nf">on</span><span class="p">(</span><span class="dl">'</span><span class="s1">talk-to-me</span><span class="dl">'</span><span class="p">,</span> <span class="nf">function </span><span class="p">(</span><span class="nx">data</span><span class="p">,</span> <span class="nx">cb</span><span class="p">)</span> <span class="p">{</span>
<span class="nf">cb</span><span class="p">(</span><span class="nx">data</span><span class="p">);</span>
<span class="p">});</span>
<span class="nx">socket</span><span class="p">.</span><span class="nf">on</span><span class="p">(</span><span class="dl">'</span><span class="s1">isLoggedIn</span><span class="dl">'</span><span class="p">,</span> <span class="nf">function </span><span class="p">(</span><span class="nx">callback</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">user</span> <span class="o">=</span> <span class="nx">socket</span><span class="p">.</span><span class="nx">request</span><span class="p">.</span><span class="nx">session</span><span class="p">.</span><span class="nx">user</span><span class="p">;</span>
<span class="k">if </span><span class="p">(</span><span class="o">!!</span><span class="nx">user</span><span class="p">)</span> <span class="p">{</span> <span class="nf">debug</span><span class="p">(</span><span class="dl">'</span><span class="s1">isLogggedIn</span><span class="dl">'</span><span class="p">,</span> <span class="nx">user</span><span class="p">);</span> <span class="p">}</span>
<span class="nf">callback</span><span class="p">(</span><span class="o">!!</span><span class="nx">user</span><span class="p">);</span>
<span class="p">});</span>
<span class="nx">socket</span><span class="p">.</span><span class="nf">on</span><span class="p">(</span><span class="dl">'</span><span class="s1">login</span><span class="dl">'</span><span class="p">,</span> <span class="nf">function </span><span class="p">(</span><span class="nx">credentials</span><span class="p">,</span> <span class="nx">callback</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">credentials</span> <span class="o">=</span> <span class="nx">JSON</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="nx">credentials</span><span class="p">);</span>
<span class="k">if </span><span class="p">(</span><span class="o">!</span><span class="nx">credentials</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="nf">callback</span><span class="p">(</span><span class="kc">false</span><span class="p">);</span>
<span class="p">}</span>
<span class="kd">var</span> <span class="nx">uname</span> <span class="o">=</span> <span class="nx">credentials</span><span class="p">.</span><span class="nx">username</span><span class="p">;</span>
<span class="kd">var</span> <span class="nx">pword</span> <span class="o">=</span> <span class="nx">credentials</span><span class="p">.</span><span class="nx">password</span><span class="p">;</span>
<span class="kd">var</span> <span class="nx">session</span> <span class="o">=</span> <span class="nx">socket</span><span class="p">.</span><span class="nx">request</span><span class="p">.</span><span class="nx">session</span><span class="p">;</span>
<span class="k">if </span><span class="p">(</span><span class="nx">uname</span> <span class="o">===</span> <span class="nx">config</span><span class="p">.</span><span class="nx">admin</span><span class="p">.</span><span class="nx">username</span> <span class="o">&&</span> <span class="nx">pword</span> <span class="o">===</span> <span class="nx">config</span><span class="p">.</span><span class="nx">admin</span><span class="p">.</span><span class="nx">password</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">session</span><span class="p">.</span><span class="nx">user</span> <span class="o">=</span> <span class="nx">uname</span><span class="p">;</span>
<span class="nf">debug</span><span class="p">(</span><span class="dl">'</span><span class="s1">login: %s</span><span class="dl">'</span><span class="p">,</span> <span class="nx">session</span><span class="p">.</span><span class="nx">user</span><span class="p">);</span>
<span class="nx">session</span><span class="p">.</span><span class="nf">save</span><span class="p">();</span>
<span class="nf">callback</span><span class="p">(</span><span class="kc">true</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">});</span>
<span class="nx">socket</span><span class="p">.</span><span class="nf">on</span><span class="p">(</span><span class="dl">'</span><span class="s1">logout</span><span class="dl">'</span><span class="p">,</span> <span class="nf">function </span><span class="p">(</span><span class="nx">callback</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">socket</span><span class="p">.</span><span class="nx">request</span><span class="p">.</span><span class="nx">session</span> <span class="o">=</span> <span class="kc">null</span><span class="p">;</span>
<span class="nf">callback</span><span class="p">(</span><span class="kc">true</span><span class="p">);</span>
<span class="p">});</span>
<span class="nx">socket</span><span class="p">.</span><span class="nf">on</span><span class="p">(</span><span class="dl">'</span><span class="s1">findQuery</span><span class="dl">'</span><span class="p">,</span> <span class="nx">findQuery</span><span class="p">);</span>
<span class="nx">socket</span><span class="p">.</span><span class="nf">on</span><span class="p">(</span><span class="dl">'</span><span class="s1">find</span><span class="dl">'</span><span class="p">,</span> <span class="nx">find</span><span class="p">);</span>
<span class="nx">socket</span><span class="p">.</span><span class="nf">on</span><span class="p">(</span><span class="dl">'</span><span class="s1">patch</span><span class="dl">'</span><span class="p">,</span> <span class="nf">function </span><span class="p">(</span><span class="nx">operation</span><span class="p">,</span> <span class="nx">callback</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if </span><span class="p">(</span><span class="o">!</span><span class="nx">socket</span><span class="p">.</span><span class="nx">request</span><span class="p">.</span><span class="nx">session</span><span class="p">.</span><span class="nx">user</span><span class="p">)</span> <span class="p">{</span>
<span class="nf">debug</span><span class="p">(</span><span class="dl">'</span><span class="s1">patch tried without user session</span><span class="dl">'</span><span class="p">);</span>
<span class="k">return</span> <span class="nf">callback</span><span class="p">(</span><span class="nx">JSON</span><span class="p">.</span><span class="nf">stringify</span><span class="p">({</span><span class="na">errors</span><span class="p">:</span> <span class="p">[</span><span class="dl">"</span><span class="s2">Login Required</span><span class="dl">"</span><span class="p">]}));</span>
<span class="p">}</span>
<span class="kd">var</span> <span class="nx">_callback</span> <span class="o">=</span> <span class="nf">function </span><span class="p">(</span><span class="nx">error</span><span class="p">,</span> <span class="nx">payload</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if </span><span class="p">(</span><span class="nx">error</span><span class="p">)</span> <span class="p">{</span>
<span class="nf">debug</span><span class="p">(</span><span class="dl">'</span><span class="s1">Patch Error!</span><span class="dl">'</span><span class="p">,</span> <span class="nx">error</span><span class="p">);</span>
<span class="nf">callback</span><span class="p">({</span><span class="na">errors</span><span class="p">:</span> <span class="nx">error</span><span class="p">});</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="nx">payload</span> <span class="o">=</span> <span class="nx">payload</span> <span class="o">||</span> <span class="nx">JSON</span><span class="p">.</span><span class="nf">stringify</span><span class="p">({</span><span class="na">code</span><span class="p">:</span> <span class="mi">204</span><span class="p">});</span>
<span class="nf">callback</span><span class="p">(</span><span class="nx">payload</span><span class="p">);</span>
<span class="nf">debug</span><span class="p">(</span><span class="dl">'</span><span class="s1">didPatch...</span><span class="dl">'</span><span class="p">,</span> <span class="nx">operation</span><span class="p">,</span> <span class="nx">payload</span><span class="p">);</span>
<span class="nx">socket</span><span class="p">.</span><span class="nx">broadcast</span><span class="p">.</span><span class="nf">emit</span><span class="p">(</span><span class="dl">'</span><span class="s1">didPatch</span><span class="dl">'</span><span class="p">,</span> <span class="nx">operation</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">};</span>
<span class="nf">patch</span><span class="p">(</span><span class="nx">operation</span><span class="p">,</span> <span class="nx">_callback</span><span class="p">);</span>
<span class="p">});</span>
<span class="nx">socket</span><span class="p">.</span><span class="nf">on</span><span class="p">(</span><span class="dl">'</span><span class="s1">disconnect</span><span class="dl">'</span><span class="p">,</span> <span class="nf">function </span><span class="p">()</span> <span class="p">{</span>
<span class="nx">io</span><span class="p">.</span><span class="nf">emit</span><span class="p">(</span><span class="dl">'</span><span class="s1">error</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">User disconnected</span><span class="dl">'</span><span class="p">);</span>
<span class="p">});</span>
<span class="p">});</span>
<span class="k">return</span> <span class="nx">io</span><span class="p">;</span>
<span class="p">};</span>
<span class="cm">/**
findQuery - uses query to find resources
@param {String} JSON strigified query object `resource` property is required
@param {Function} callback
@private
**/</span>
<span class="kd">function</span> <span class="nf">findQuery</span><span class="p">(</span><span class="nx">query</span><span class="p">,</span> <span class="nx">callback</span><span class="p">)</span> <span class="p">{</span>
<span class="nf">debug</span><span class="p">(</span><span class="dl">'</span><span class="s1">findQuery...</span><span class="dl">'</span><span class="p">,</span> <span class="nx">query</span><span class="p">);</span>
<span class="k">if </span><span class="p">(</span><span class="k">typeof</span> <span class="nx">query</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">string</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">query</span> <span class="o">=</span> <span class="nx">JSON</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="nx">query</span><span class="p">);</span>
<span class="p">}</span>
<span class="kd">var</span> <span class="nx">resource</span> <span class="o">=</span> <span class="nx">query</span><span class="p">.</span><span class="nx">resource</span><span class="p">;</span>
<span class="k">delete</span> <span class="nx">query</span><span class="p">.</span><span class="nx">resource</span><span class="p">;</span>
<span class="kd">var</span> <span class="nx">_cb</span> <span class="o">=</span> <span class="nx">callback</span><span class="p">;</span>
<span class="nx">db</span><span class="p">.</span><span class="nf">findQuery</span><span class="p">(</span><span class="nx">resource</span><span class="p">,</span> <span class="nx">query</span><span class="p">,</span> <span class="nf">function </span><span class="p">(</span><span class="nx">err</span><span class="p">,</span> <span class="nx">payload</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if </span><span class="p">(</span><span class="nx">err</span><span class="p">)</span> <span class="p">{</span>
<span class="nf">debug</span><span class="p">(</span><span class="nx">err</span><span class="p">);</span>
<span class="nx">payload</span> <span class="o">=</span> <span class="p">{</span> <span class="na">errors</span><span class="p">:</span> <span class="p">{</span> <span class="na">code</span><span class="p">:</span> <span class="mi">500</span><span class="p">,</span> <span class="na">error</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Server failure</span><span class="dl">'</span> <span class="p">}</span> <span class="p">};</span>
<span class="p">}</span>
<span class="nf">_cb</span><span class="p">(</span><span class="nx">payload</span><span class="p">);</span>
<span class="p">});</span>
<span class="p">}</span>
<span class="cm">/**
find - uses query to find resources by id or slug
@param {String} JSON strigified query object requires `resource`, `id` properties
@param {Function} callback
@private
**/</span>
<span class="kd">function</span> <span class="nf">find</span><span class="p">(</span><span class="nx">query</span><span class="p">,</span> <span class="nx">callback</span><span class="p">)</span> <span class="p">{</span>
<span class="nf">debug</span><span class="p">(</span><span class="dl">'</span><span class="s1">find...</span><span class="dl">'</span><span class="p">,</span> <span class="nx">query</span><span class="p">);</span>
<span class="k">if </span><span class="p">(</span><span class="k">typeof</span> <span class="nx">query</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">string</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">query</span> <span class="o">=</span> <span class="nx">JSON</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="nx">query</span><span class="p">);</span>
<span class="p">}</span>
<span class="kd">var</span> <span class="nx">resource</span> <span class="o">=</span> <span class="nx">query</span><span class="p">.</span><span class="nx">resource</span><span class="p">;</span>
<span class="k">delete</span> <span class="nx">query</span><span class="p">.</span><span class="nx">resource</span><span class="p">;</span>
<span class="kd">var</span> <span class="nx">id</span> <span class="o">=</span> <span class="nx">query</span><span class="p">.</span><span class="nx">id</span><span class="p">;</span>
<span class="k">delete</span> <span class="nx">query</span><span class="p">.</span><span class="nx">id</span><span class="p">;</span>
<span class="kd">var</span> <span class="nx">_cb</span> <span class="o">=</span> <span class="nx">callback</span><span class="p">;</span>
<span class="kd">var</span> <span class="nx">errorPayload</span> <span class="o">=</span> <span class="p">{</span> <span class="na">errors</span><span class="p">:</span> <span class="p">{</span> <span class="na">code</span><span class="p">:</span> <span class="mi">500</span><span class="p">,</span> <span class="na">error</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Server failure</span><span class="dl">'</span> <span class="p">}</span> <span class="p">};</span>
<span class="nx">db</span><span class="p">.</span><span class="nf">find</span><span class="p">(</span><span class="nx">resource</span><span class="p">,</span> <span class="nx">id</span><span class="p">,</span> <span class="nf">function </span><span class="p">(</span><span class="nx">err</span><span class="p">,</span> <span class="nx">payload</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if </span><span class="p">(</span><span class="nx">err</span><span class="p">)</span> <span class="p">{</span>
<span class="nf">_cb</span><span class="p">(</span><span class="nx">errorPayload</span><span class="p">);</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="k">if </span><span class="p">(</span><span class="nx">payload</span><span class="p">[</span><span class="nx">resource</span><span class="p">]</span> <span class="o">!==</span> <span class="kc">null</span><span class="p">)</span> <span class="p">{</span>
<span class="nf">_cb</span><span class="p">(</span><span class="nx">payload</span><span class="p">);</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="nx">db</span><span class="p">.</span><span class="nf">findBySlug</span><span class="p">(</span><span class="nx">resource</span><span class="p">,</span> <span class="nx">id</span><span class="p">,</span> <span class="nf">function </span><span class="p">(</span><span class="nx">err</span><span class="p">,</span> <span class="nx">payload</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if </span><span class="p">(</span><span class="nx">err</span><span class="p">)</span> <span class="p">{</span>
<span class="nf">_cb</span><span class="p">(</span><span class="nx">errorPayload</span><span class="p">);</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="k">if </span><span class="p">(</span><span class="nx">payload</span><span class="p">[</span><span class="nx">resource</span><span class="p">]</span> <span class="o">!==</span> <span class="kc">null</span><span class="p">)</span> <span class="p">{</span>
<span class="nf">_cb</span><span class="p">(</span><span class="nx">payload</span><span class="p">);</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="nf">_cb</span><span class="p">({</span> <span class="na">errors</span><span class="p">:</span> <span class="p">{</span> <span class="na">code</span><span class="p">:</span> <span class="mi">404</span><span class="p">,</span> <span class="na">error</span><span class="p">:</span> <span class="dl">'</span><span class="s1">Not Found</span><span class="dl">'</span> <span class="p">}</span> <span class="p">});</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">});</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">});</span>
<span class="p">}</span>
<span class="kd">function</span> <span class="nf">patch</span><span class="p">(</span><span class="nx">operation</span><span class="p">,</span> <span class="nx">callback</span><span class="p">)</span> <span class="p">{</span>
<span class="nf">debug</span><span class="p">(</span><span class="dl">'</span><span class="s1">patch...</span><span class="dl">'</span><span class="p">,</span> <span class="nx">operation</span><span class="p">);</span>
<span class="k">if </span><span class="p">(</span><span class="k">typeof</span> <span class="nx">operation</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">string</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">operation</span> <span class="o">=</span> <span class="nx">JSON</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="nx">operation</span><span class="p">);</span>
<span class="p">}</span>
<span class="kd">var</span> <span class="nx">path</span> <span class="o">=</span> <span class="nx">operation</span><span class="p">.</span><span class="nx">path</span><span class="p">.</span><span class="nf">split</span><span class="p">(</span><span class="dl">'</span><span class="s1">/</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">type</span> <span class="o">=</span> <span class="nx">path</span><span class="p">[</span><span class="mi">1</span><span class="p">];</span>
<span class="kd">var</span> <span class="nx">id</span> <span class="o">=</span> <span class="nx">path</span><span class="p">[</span><span class="mi">2</span><span class="p">];</span>
<span class="kd">var</span> <span class="nx">prop</span> <span class="o">=</span> <span class="nx">path</span><span class="p">[</span><span class="mi">3</span><span class="p">];</span> <span class="c1">// REVIEW support sub-path?</span>
<span class="k">if </span><span class="p">(</span><span class="nx">prop</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">links</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">link</span> <span class="o">=</span> <span class="nx">path</span><span class="p">[</span><span class="mi">4</span><span class="p">];</span>
<span class="nf">patchLinks</span><span class="p">(</span><span class="nx">type</span><span class="p">,</span> <span class="nx">id</span><span class="p">,</span> <span class="nx">link</span><span class="p">,</span> <span class="nx">operation</span><span class="p">,</span> <span class="nx">callback</span><span class="p">);</span>
<span class="p">}</span> <span class="k">else</span> <span class="k">if </span><span class="p">(</span><span class="nx">operation</span><span class="p">.</span><span class="nx">op</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">replace</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">payload</span> <span class="o">=</span> <span class="p">{};</span>
<span class="nx">payload</span><span class="p">[</span><span class="nx">prop</span><span class="p">]</span> <span class="o">=</span> <span class="nx">operation</span><span class="p">.</span><span class="nx">value</span><span class="p">;</span>
<span class="nx">db</span><span class="p">.</span><span class="nf">updateRecord</span><span class="p">(</span><span class="nx">type</span><span class="p">,</span> <span class="nx">id</span><span class="p">,</span> <span class="nx">payload</span><span class="p">,</span> <span class="nx">callback</span><span class="p">);</span>
<span class="p">}</span> <span class="k">else</span> <span class="k">if </span><span class="p">(</span><span class="nx">operation</span><span class="p">.</span><span class="nx">op</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">remove</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">db</span><span class="p">.</span><span class="nf">deleteRecord</span><span class="p">(</span><span class="nx">type</span><span class="p">,</span> <span class="nx">id</span><span class="p">,</span> <span class="nx">callback</span><span class="p">);</span>
<span class="p">}</span> <span class="k">if </span><span class="p">(</span><span class="nx">operation</span><span class="p">.</span><span class="nx">op</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">add</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">db</span><span class="p">.</span><span class="nf">createRecord</span><span class="p">(</span><span class="nx">type</span><span class="p">,</span> <span class="nx">operation</span><span class="p">.</span><span class="nx">value</span><span class="p">,</span> <span class="nx">callback</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="kd">function</span> <span class="nf">patchLinks</span><span class="p">(</span><span class="nx">type</span><span class="p">,</span> <span class="nx">id</span><span class="p">,</span> <span class="nx">linkName</span><span class="p">,</span> <span class="nx">operation</span><span class="p">,</span> <span class="nx">callback</span><span class="p">)</span> <span class="p">{</span>
<span class="nf">debug</span><span class="p">(</span><span class="dl">'</span><span class="s1">patchLinks...</span><span class="dl">'</span><span class="p">,</span> <span class="nx">type</span><span class="p">,</span> <span class="nx">id</span><span class="p">,</span> <span class="nx">linkName</span><span class="p">,</span> <span class="nx">operation</span><span class="p">);</span>
<span class="nf">find</span><span class="p">({</span><span class="na">resource</span><span class="p">:</span> <span class="nx">type</span><span class="p">,</span> <span class="na">id</span><span class="p">:</span> <span class="nx">id</span><span class="p">},</span> <span class="nf">function </span><span class="p">(</span><span class="nx">record</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if </span><span class="p">(</span><span class="o">!</span><span class="nx">record</span> <span class="o">||</span> <span class="nx">record</span> <span class="o">&&</span> <span class="nx">record</span><span class="p">.</span><span class="nx">errors</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">errors</span> <span class="o">=</span> <span class="p">(</span><span class="nx">record</span><span class="p">)</span> <span class="p">?</span> <span class="nx">record</span><span class="p">.</span><span class="nx">errors</span> <span class="p">:</span> <span class="p">[];</span>
<span class="nf">debug</span><span class="p">(</span><span class="dl">'</span><span class="s1">Error finding resource for patchLinks action</span><span class="dl">'</span><span class="p">,</span> <span class="nx">errors</span><span class="p">);</span>
<span class="nf">callback</span><span class="p">(</span><span class="nx">errors</span><span class="p">);</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">path</span> <span class="o">=</span> <span class="nx">operation</span><span class="p">.</span><span class="nx">path</span><span class="p">.</span><span class="nf">split</span><span class="p">(</span><span class="nx">linkName</span><span class="p">);</span>
<span class="nx">path</span> <span class="o">=</span> <span class="p">(</span><span class="nx">path</span><span class="p">)</span> <span class="p">?</span> <span class="nx">path</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="p">:</span> <span class="kc">null</span><span class="p">;</span>
<span class="kd">var</span> <span class="nx">value</span> <span class="o">=</span> <span class="nx">operation</span><span class="p">.</span><span class="nx">value</span><span class="p">;</span>
<span class="kd">var</span> <span class="nx">op</span> <span class="o">=</span> <span class="nx">operation</span><span class="p">.</span><span class="nx">op</span><span class="p">;</span>
<span class="kd">var</span> <span class="nx">payload</span> <span class="o">=</span> <span class="nx">record</span><span class="p">[</span><span class="nx">type</span><span class="p">];</span>
<span class="nx">payload</span><span class="p">.</span><span class="nx">links</span> <span class="o">=</span> <span class="nx">payload</span><span class="p">.</span><span class="nx">links</span> <span class="o">||</span> <span class="p">{};</span>
<span class="nx">payload</span><span class="p">.</span><span class="nx">links</span><span class="p">[</span><span class="nx">linkName</span><span class="p">]</span> <span class="o">=</span> <span class="nx">payload</span><span class="p">.</span><span class="nx">links</span><span class="p">[</span><span class="nx">linkName</span><span class="p">]</span> <span class="o">||</span> <span class="p">[];</span>
<span class="k">if </span><span class="p">(</span><span class="nx">op</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">add</span><span class="dl">'</span> <span class="o">&&</span> <span class="nx">path</span><span class="p">.</span><span class="nf">match</span><span class="p">(</span><span class="sr">/</span><span class="se">\-</span><span class="sr">$/</span><span class="p">)</span> <span class="o">!==</span> <span class="kc">null</span> <span class="o">&&</span> <span class="nx">value</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">payload</span><span class="p">.</span><span class="nx">links</span><span class="p">[</span><span class="nx">linkName</span><span class="p">].</span><span class="nf">push</span><span class="p">(</span><span class="nx">value</span><span class="p">);</span>
<span class="p">}</span> <span class="k">else</span> <span class="k">if </span><span class="p">(</span><span class="nx">value</span> <span class="o">&&</span> <span class="nx">op</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">add</span><span class="dl">'</span> <span class="o">||</span> <span class="nx">op</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">replace</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">payload</span><span class="p">.</span><span class="nx">links</span><span class="p">[</span><span class="nx">linkName</span><span class="p">]</span> <span class="o">=</span> <span class="nx">value</span><span class="p">;</span>
<span class="p">}</span> <span class="k">else</span> <span class="k">if </span><span class="p">(</span><span class="nx">op</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">remove</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">linkId</span> <span class="o">=</span> <span class="nx">path</span><span class="p">.</span><span class="nf">split</span><span class="p">(</span><span class="dl">'</span><span class="s1">/</span><span class="dl">'</span><span class="p">);</span>
<span class="k">if </span><span class="p">(</span><span class="nx">linkId</span><span class="p">.</span><span class="nx">length</span> <span class="o">></span> <span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">linkId</span> <span class="o">=</span> <span class="nx">linkId</span><span class="p">[</span><span class="mi">1</span><span class="p">];</span>
<span class="kd">var</span> <span class="nx">idx</span> <span class="o">=</span> <span class="nx">payload</span><span class="p">.</span><span class="nx">links</span><span class="p">[</span><span class="nx">linkName</span><span class="p">].</span><span class="nf">indexOf</span><span class="p">(</span><span class="nx">linkId</span><span class="p">);</span>
<span class="nx">payload</span><span class="p">.</span><span class="nx">links</span><span class="p">[</span><span class="nx">linkName</span><span class="p">].</span><span class="nf">splice</span><span class="p">(</span><span class="nx">idx</span><span class="p">,</span> <span class="mi">1</span><span class="p">);</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="nx">payload</span><span class="p">.</span><span class="nx">links</span><span class="p">[</span><span class="nx">linkName</span><span class="p">]</span> <span class="o">=</span> <span class="kc">null</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="nx">db</span><span class="p">.</span><span class="nf">updateRecord</span><span class="p">(</span><span class="nx">type</span><span class="p">,</span> <span class="nx">id</span><span class="p">,</span> <span class="p">{</span><span class="na">links</span><span class="p">:</span> <span class="nx">payload</span><span class="p">.</span><span class="nx">links</span><span class="p">},</span> <span class="nx">callback</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">});</span>
<span class="p">}</span>
<span class="c1">// TODO Use Ember.Inflector or other Inflector?</span>
<span class="kd">function</span> <span class="nf">singularize</span><span class="p">(</span><span class="nx">name</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">name</span><span class="p">.</span><span class="nf">slice</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="nx">name</span><span class="p">.</span><span class="nx">length</span> <span class="o">-</span> <span class="mi">1</span><span class="p">);</span>
<span class="p">}</span>
<span class="kd">function</span> <span class="nf">pluralize</span><span class="p">(</span><span class="nx">name</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">name</span> <span class="o">+</span> <span class="dl">'</span><span class="s1">s</span><span class="dl">'</span><span class="p">;</span>
<span class="p">}</span>
</code></pre>We are EmberConf 2014https://pixelhandler.dev/posts/we-are-emberconf-20142014-03-30T00:00:00+00:002014-03-30T00:00:00+00:00Billy HeatonI may have missed a few segments of the presentations or been distracted during a few talks so I apologize in advance to the speakers if I have not included notes from your talks in this article. I...<p>I may have missed a few segments of the presentations or been distracted during a few talks so I apologize in advance to the speakers if I have not included notes from your talks in this article. I can honestly say that I did find value in every presentation. (That has not always been the case when I've attended a conference.) The headings I use in this article may not match the speakers presentation titles. This article captures what I learned from the conference so I've taken liberty to create new headings and combine common topics; besides "Keynote" as a heading lacks context ;)</p>
<h2>Speed, Efficiency, Productivity...</h2>
<p>The take away is that by adopting the conventions of the Ember.js framework a developer gets "Speed, Efficiency, Productivity..." by default.</p>
<p>Flows are just as important as screens. With Ember you control (users') flow through your Web application using the <code>Router</code>. The routing features that Ember provides are more than just URLs. This was proven by more that one presentation on the topic of routing. There was a very impressive presentation later on <a href="proxy.php?url=https://fnd.io" target="_blank" rel="noopener noreferrer">fnd.io</a> which used every model known by the application to route any type of object that a search application could identify. And another presentation that injected additional logic to manage transitions between complex states in a banking like Web application.</p>
<p>"Get rid of sinkholes, use commonality" Routing provides strong defaults in how to organize flow and state in a true Web application.</p>
<p>My experience with the router has been that once I grok how to use it the next time I start an application the productivity is noticeable and satisfying. The same can be said of other conventions that Ember.js provides. The concepts in the router are simple and complex at the same time; but the net take away is that using this common way of managing flow and state yields productivity. There is a reward for investing time in learning and using the conventions of routing for a browser application. </p>
<h2>Frameworks are Designed to Nudge You in the Right Direction</h2>
<p>The Vine app for sharing videos is built with Ember and it turns out that using many many videos on a page can be a bit of a challenge in a Web application; especially for creating a snappy user experience. Data visualization can be a complex chore for a team with varying strengths. Ember <code>Component</code> can be used to encapsulate complexity of using a 3rd party library like D3 for pretty data on screen. Application code can be built with more than one recipe to deliver a different experience by only changing the dependency for a router file.</p>
<p>I find that once I get the concepts of using components to isolate behavior and reuse it in various contexts that is a very powerful and valuable convention. And Web components are in the not so far off future already. </p>
<p>Welcome to the future :) (Not really.) But I do think that using Ember components is a peek into what will likely be a shared way build reusable components on the Web in general.</p>
<h2>Contributing</h2>
<p>The contributing presentation provided great details on how to get involved and help the community. The TL;DR summary is do try to make it easy for the core team to help you help the community.</p>
<p>It would be a good idea to subscribe to the security email list. Also you can enable feature flags in the beta/release channels of Ember but DON'T DO IT. The fixes to bugs found in new features will not be patched onto those release channels. It's best to use Canary to try and contribute to new features or bugs.</p>
<p>I think this talk exposed a common and priceless value of the Ember community in general... there is a huge value that is given by the core team members as well as those who decide to contribute and any level. This value is sacrificial and comes at a very high price, the time belonging to loved ones. I would conclude that there is a genuine passion for the project itself as well as the people who use the project's codebases.</p>
<p>The fast track to helping with the community is an attitude of others first. Adopting the conventions on how to commit code for a feature or a bug makes peoples lives better.</p>
<h2>Love Trolls</h2>
<p>Trolling is common on the interwebs, love trolling not as much. Well, unless you're at EmberConf.</p>
<p>The phase "Just Stop" is all it takes to find common ground between such a wide variety of world views within the development community.</p>
<p>I think this expression represents a care for humans in general, like... "Hey my friend, please don't make a fool of yourself" or, "Hey I just need you to know I'm not ok with that, can we move on to common ground". </p>
<p>Just as we are capable of introducing bugs to a codebase, we are all capable of hurting others. More important than an awesome framework are awesome people who aren't left out. </p>
<p>This idea rises above political correctness and just puts others before self; a valuable ethic for any community. And, it's found in the Ember community.</p>
<h2>Data Persistence</h2>
<h3>...A Hard Problem</h3>
<ul>
<li>Data Transformation</li>
<li>Asynchrony</li>
<li>Caching</li>
<li>Locality</li>
<li>Relationships</li>
</ul>
<p>The above are the objectives that Ember Data achieves to solve for Web applications. However it's not as easy as one would expect. When all the above happen together, there be dragons. </p>
<p>The premise of the Ember Data project are...</p>
<ol>
<li>Easy problems should be locked down less often (give developers more flexibility)</li>
<li>Lock down API for hard problem</li>
</ol>
<h3>...You're Building A Distributed Computing System</h3>
<p>We received a lecture on math theory related to distributed computing systems. There is a trade-off between availability and consistency. Safety (always right) or liveness (eventually there), how would you like your data? A good goal is to build weak consistency with higher availability. Something to consider is that once you send data to a client it immediately becomes a cache of that data (and most likely to become stale quickly).</p>
<p>I forgot how much I enjoy mathematics and theory, it was refreshing to think about the client-server data caching problem from this perspective. A server-based data driven application should know how to manage the data cache sent to it's clients, or perhaps a client application should know it's contract for the data it receives. One thing I'd like to work on is using sockets to manage multiple data stores (caches): perhaps a memory store, a local store (disc) and a remote store communicating with clients via Web sockets.</p>
<h2>Tooling: Build Your App with Broccoli</h2>
<p>Remember those little veggie trees? Your application's modules (files) live in trees (folders), the broccoli build library only knows about trees, <code>read</code>, <code>read</code>, and <code>cleanup</code>. The API is small leaving room for adding specific sets of functionality by writing plugins. You can have many plugins and build targets as well as a <code>mergeTrees</code> method to package up your application.</p>
<p>Broccoli is for working with files in your repository it's not a task runner. Integration within your tool set can be achieved as you choose, perhaps a grunt task kick off the build.</p>
<p>The benefit of using <code>broccoli</code> to build your application is... only files with a change in last modified date are re-processed so the result a super speedy build with each change; even when you have a large set of files (modules) composing your application.</p>
<p>Broccoli is the newest choice of tooling for generating a build for a JavaScript application. And its likely the fastest too. What a considerable contribution to the Ember community. I am looking forward to a speed boost in my development cycles.</p>
<h2>Animation and Transitions, Janky Not Allowed</h2>
<p>Why animate? Just because developers understand crazy abstractions, not everyone is "freakish like that". Providing some direction to flow by animating a change gives users a sense of where they are navigating to and how to go back to where they've been. If every link just immediately appeared users would have a limited sense of what the application's flow is. As users transition between screens or even parts of a screen, animation provides some directional context that makes the application feel more natural.</p>
<p>Using Ember there are two types of animations. One is a simple animation within a single route, done with CSS (based on class attributes that belong to a component on the screen). The second is a complex animation between route transitions.</p>
<p>To avoid jankiness in an Ember application it is a good idea to silence browser events so that your application is not listening or responding to irrelevant event handlers. One solution is to add back in needed event handlers by wrapping the code needing the events with a component which does listen for the event. A couple concepts to hook into so wrangle complex animations are using <code>willTransition</code> and also named outlets. For thinks like a modal using <code>query-params-new</code> allows you to bypass router and reuse the same modal anywhere.</p>
<h2>Angular's Transclusion is not a Misnomer</h2>
<p>Transclusion is the inclusion of one thing into something else. It's like using an Ember.Component instance with <code>yield</code>. I think the goal of using a directive with transclusion is to encapsulate HTML, CSS and JavaScript, to roll your own reusable widgets. Perhaps a dependent select box is a good example of a Web Component that would utilize Angular directives and transclusion; alternatively, an Ember.Component would be used for the same.</p>
<p>I'm not sure that I really know why there was a Talk on Angular other than to call out that in some cases we are working on common solutions in both development communities; however the techniques and naming may be quite different to achieve the same goal. I did like how the speaker tackled a couple topics which those in the Ember community have poked fun at perhaps at the expense of those in the Angular community. At the end of the day we're all JavaScript developers.</p>
<p>I think that the speaker on the Angular topic was quite brave to take on this topic for a decidedly "Ember" audience. For me the take away is that there are different strokes for different folks; and that's just fine; perhaps we'll learn something from each other of find some common ground in an area like Web components.</p>
<h2>Doing Better with Available API's Using Ember.js</h2>
<p>Searching the iTunes Music Store and App store can be at times not so great of an experience, especially on the Web. Apple's API's to query media types make a good source to build an application built without a backend, and perhaps do a better job on the Web than Apple has done. <a href="proxy.php?url=http://fnd.io" target="_blank" rel="noopener noreferrer">Fnd.io</a> is the example that takes on Apple's search experience on the Web platform head on. </p>
<p>A talk was presented on how to model iTunes Media using Mixins and Ember Data. Using composition to model various data types found in the stores' API gave some flexibility to identify types from an API where it's not always clear what things actually are typed as. The fnd.io site focuses on product search rather than document search.</p>
<p>The value Ember provided was beautiful URLs with a fast product search tool that rivals any experience searching for iTunes and App store products on the web.</p>
<p>"Tapas with Ember" (Brunch recipe) may have been a result of this project which uses Brunch for a build tool.</p>
<h2>2X-3X Performance Boost Coming Soon: HTMLBars</h2>
<p>The project is still in an alpha stage; though it was not possible to release an alpha version for EmberConf2014 serious effort was made to do so. The promise is 2X-3X times speed gains once HTMLBars ships. I'm looking forward to kissing metamorphs good-bye and saying hello to "fast" with HTMLBars.</p>
<p>Some of the differences in HTMLBars are:</p>
<ul>
<li>Unlike Handlebars HTMLBars understands your markup</li>
<li>Builds DOM fragments instead of Strings</li>
<li>No more <code>script</code> tags (MetaMorphs)</li>
<li>Large lists are much more performance, as the source uses <code>clone</code></li>
<li>Binding update order</li>
<li>Stop recursion / Limits re-render (anti-pattern)</li>
<li>Simplicity in the markup, removes the need for helpers like <code>bind-attr</code></li>
</ul>
<p>Ultimately the goal of HTMLBars is to step into a simpler primitive.</p>
<p>I could tell that the team working on the project was a bit spent and also sensed that a significant sacrifice of personal time has been invested in toward increased performance for the Ember community. Thanks for the hard work guys; and thanks to your loved ones for leasing you to the Ember community for this project.</p>
<h2>Reusable Views: Components</h2>
<p>A couple questions to ask when considering to create a component are:</p>
<ul>
<li>Does it have behavior?</li>
<li>Does it need to use a specific class or action?</li>
</ul>
<p>Good examples are <code>form</code> with <code>input type=submit</code>; and <code>select</code> with <code>option</code>. The behavior of a form change when there is a submit input element.</p>
<p>Some advice given regarding building with Ember:</p>
<ul>
<li>Don't build large components with big templates</li>
<li>Do use composite components; lean on using no templates</li>
<li>Test and develop with dynamic data</li>
<li>Build native Ember components rather then using Bootstrap</li>
<li>Child components should inform parent they exist to change behavior, <code>willInsertElement</code> is a good hook to use</li>
</ul>
<p><code>ic-tabs</code> and <code>ic-menu</code> component micro-libraries were demonstrated. I good goal for an architect of an application is to measure success by the frequency that your team asks how to use the apis provided to the application developers. Using components to abstract complexity and provide consistency may reduce the amount of support needed. Also using method names that define the action and combining the <code>.on('eventName')</code> extension to the method helps communicate the how and why of the methods contained in your components. Thank you <a href="proxy.php?url=https://github.com/instructure" target="_blank" rel="noopener noreferrer">Instructure</a> team, for contributing these components to the Ember community.</p>
<h2>Ember AppKit Gives Birth to Ember CLI</h2>
<p><code>ember-cli</code> is the child project conceived by the bleeding edge work done on Ember App Kit (EAK). While creating new tooling for Ember projects, the EAK project tackled various problems and paved the way to abstract many solutions. Using Ember App Kit allows developers to ship real stuff and also experiment with solutions at the same time. It's my opinion that good things come from real apps. So, thank you to those to felt comfortable living on the bleeding edge, your pains have born <code>ember-cli</code>.</p>
<p>Some of the byproducts of the EAK project are:</p>
<ol>
<li><em>Problem</em>: Coupling / <em>Solution</em>: Inversion of control, <code>resolver</code> knows/finds in <code>container</code></li>
<li><em>Problem</em>: Assemble Source / <em>Solution</em>: Build Tooling</li>
<li><em>Problem</em>: Upgrade nightmares / <em>Solution</em>: Better abstractions, hide nearly all the complexity</li>
<li><em>Problem</em>: Copious bugs / <em>Solution</em>: Thorough test suite</li>
<li><em>Problem</em>: Build Stability / Solution: Tool choice, build pipeline with Broccoli</li>
</ol>
<p>"When a problem comes along you must whip it" - My perception of EAK's theme</p>
<p><code>ember-cli</code> is the single place to focus using the solutions above. You can help too! A call to action was echoed from the podium, "we need a good story for fingerprinting". (Perhaps using md5 hash in asset filenames and updating links to assets using the same hash.) This is not a new problem but needs a good story for JavaScript applications.</p>
<p>Perhaps the most noticeable result of EAK was the adoption of using es6 modules in Ember applications today. (Welcome to the future.)</p>
<h2>Give Back, You Can Make a Difference</h2>
<p>You can teach a web development class as long as your students can type. DeVaris Brown did just that, and proved that it's possible to teach 16 to 19 year old kids Web development. His story was amazing, heartfelt and received with many cheers and tears alike. As developers we have a highly valuable skill and DeVaris has carved out curriculum for teaching the basic building blocks of Web development to kids. Sounds like madness no? Well for Devaris there were more than a few magical moments. He said that the kids "liked the magic of the Ember.js framework". This was caught after kids asked if there was another way beside copy and paste to create Web pages. Aha, you can build dynamic web pages with Ember.js; magic right! It appears so.</p>
<p>What Devaris strived to do was make a difference for underprivileged kids using what he knows. He learning that the basic requirement for accepting students is the ability to type; he discovered that people can do good in their communities. The kids worked on a project to support connecting people, foodbanks, and food providers; how awesome that is!</p>
<p>Imagine explaining the DOM to kids and teaching HTML, CSS, and JavaScript basics. Devaris committed to sharing his curriculum (a collection of Markdown files) on github. He also mentioned that he found <a href="proxy.php?url=http://typing.io" target="_blank" rel="noopener noreferrer">typing.io</a> to be a good tool to help with the typing challenges, "kids only use a few characters while texting".</p>
<p>I was very impressed with how Devaris hung in there until that <strong>aha</strong> moment when the kids caught it; Devaris was like "Thank you Jesus!" and we all were clapping and crying. Nice work Devaris, you made a difference and inspired others to do the same.</p>
<h2>Who Owns Query Params, Router or Controller?</h2>
<p>Mr. Router thought that the <code>Router</code> should own query params, yet there was a convincing case for the <code>Controller</code> to manage these variables. <code>query-params-new</code> has a controller-centric API.</p>
<p>There is a problem to solve, controller property stickiness, perhaps a new primitive "model dependent state" may be the resolution. This concept (vaporware at this point in time) would scope controller state to a model instance. The issue of controller stickiness is not unique to query-params.</p>
<p>My take away is that things that seem simple can become hard; and until a feature becomes stable it remains behind a feature flag like <code>query-params-new</code>.</p>
<h2>Ember.js Testing is Not Hard, It's Unknown</h2>
<p>The <code>ember-qunit</code> project has a great concept that makes testing an Ember application much easier that is has been in the past. The <code>resolver</code> is used to pluck out the subject of the test into an new container which sets up a sandbox for unit testing.</p>
<p>Ember's testing backstory (the happy path using QUnit) was presented with funny slides. It's been a year since we've had the <code>ember-testing</code> package, and there has been additional helpers added to the project as well.</p>
<p>Integration testing is fairy straight forward and now with <code>ember-qunit</code> unit testing has an improved story. To setup and insert the test helpers call <code>setupForTesting()</code> and <code>injectTestHelpers()</code> on your application namespace. The (global) helpers are <code>visit</code>, <code>click</code>, <code>fillIn</code>, <code>find</code>, <code>findWithAssert</code>, <code>keyEvent</code>, <code>andThen</code>; the newest are: <code>triggerEvent</code>, <code>currentRouteName</code>. See the API docs for more info on using the helpers.</p>
<p>Perhaps the best way to get started with reading example tests:</p>
<ul>
<li><a href="proxy.php?url=http://jsbin.com/suteg/7/edit?html,js,output" target="_blank" rel="noopener noreferrer">jsbin.com/suteg</a></li>
<li><a href="proxy.php?url=http://jsbin.com/cahuc/1/edit?html,js,output" target="_blank" rel="noopener noreferrer">jsbin.com/cahuc</a></li>
<li><a href="proxy.php?url=http://jsbin.com/qifon/5/edit?html,js,output" target="_blank" rel="noopener noreferrer">jsbin.com/qifon</a></li>
<li><a href="proxy.php?url=http://jsbin.com/numof/9/edit?html,js,output" target="_blank" rel="noopener noreferrer">jsbin.com/numof</a></li>
<li><a href="proxy.php?url=http://jsbin.com/witut/7/edit?html,js,output" target="_blank" rel="noopener noreferrer">jsbin.com/witut</a></li>
<li><a href="proxy.php?url=http://jsbin.com/kilej/3/edit?html,js,output" target="_blank" rel="noopener noreferrer">jsbin.com/kilej</a></li>
</ul>
<p>And also, don't forget to checkout the new guide in progress:</p>
<ul>
<li><a href="proxy.php?url=https://github.com/emberjs/website/pull/1401" target="_blank" rel="noopener noreferrer">Initial PR for ember testing guide redux</a></li>
</ul>
<p>I had taken a stab a writing a testing guide awhile back, <a href="proxy.php?url=https://github.com/emberjs/website/pull/610/files" target="_blank" rel="noopener noreferrer">website/pull/610</a>, which I closed out as the testing story was still a bit painful and we really needed something like <code>ember-qunit</code> to evolve and simplify isolating tests.</p>
<p>Thanks to everyone involved in helping the Ember testing story evolve from painful to simple; I'm looking forward to an awesome testing guide.</p>
<h2>Controlling Flow in the Router</h2>
<p>Traversing routes to enforce a specific flow event with optional flows can be achieved. The example presented was a login flow to a backing application with various options that depend on the user's choices made while stepping through this flow.</p>
<p>When designing flows, start by a) listing every single permutation, b) inventory all routes, c) list linear paths, d) contain state modification on nodes, by describing state on each node; finally e) traverse backwards (back button). The route's <code>replaceWith</code> method is well suited for using custom flow.</p>
<p>The project presented is <a href="proxy.php?url=https://github.com/nathanhammond/ember-flows" target="_blank" rel="noopener noreferrer">ember-flows</a>; which is a great example of extending functionality by injecting an object into your application container.</p>
<p>The object used in to define flow strategy has properties for <code>from</code> (route path), <code>to</code> (route path), <code>weight</code> (number), and <code>conditions</code> (Array). And the hook used to enforce the flow is <code>beforeModel</code> like so <code>this.get('flow').check();</code>. The flow object injected into the application can make a choice about when to change paths in the router using <code>replaceWith</code>. Well hopefully I grokked it correctly see the <code>ember-flows</code> project if you'd like the details.</p>
<p>Again I's great to know that there are solutions that are essentially plugin-able to being shared that are based on solutions to complex problems.</p>
<h2>Snappy Performance Makes Users Happy</h2>
<p>How fast is fast?</p>
<ul>
<li>16ms : 60 fps is <strong>Fast</strong></li>
<li>300ms: is <strong>Snappy</strong></li>
<li>1s+ is like I'm <strong>waiting</strong></li>
<li>10s+ is bad, you just <strong>lost a visitor</strong></li>
</ul>
<p>When working with Ember applications remember that the Ember part is a subset of the code that affects Web performance (page speed). A good goal is to provide a snappy response which is under 1 second; about 300 milliseconds. When measuring page speed, the visual performance, how a visitor perceives responsiveness should be considered as well. Recording a video of the page load and marking when the page looks ready is an indicator that should be weighted along other speed measurements like first load time, document ready, etc.</p>
<p>Methodology</p>
<ol>
<li>Gather Facts</li>
<li>Analyze and Theorize</li>
<li>Change a Single Thing</li>
<li>Confirm your Theory</li>
</ol>
<p>Also when testing it's a good idea to disable browser extensions. "Browser Networking" is a book we should all own and reference. Getting familiar with the browser's development tools for performance is what is needed to analyze and test performance optimizations. The flame chart in a render snapshot can expose problematic cycles of re-rendering and performance sinkholes. Also property change notifications can affect performance. Instead of looping over a collection and inserting or setting properties during each iteration, look for ways to complete enumerations then make an assignment of a collection to an object property.</p>
<p>I think it was mentioned that there should be a book on this topic coming soon. See the speakers post on the talk here: <a href="proxy.php?url=http://madhatted.com/2014/3/28/via-emberconf-performance-in-ember-apps" target="_blank" rel="noopener noreferrer">Performance in Ember Apps</a></p>
<h2>Evolution: The Extensible Web</h2>
<p>Open source is awesome! Remember when Netscape pushed it's source code to the Web. That was a Revolution. The JavaScript development lifecycle is still evolving. </p>
<ul>
<li>Focus: don't re-invent the universe</li>
<li>Consistency: keep unified development model</li>
<li>Adoption: gradual adoption with polyfills, compilers</li>
</ul>
<p>One example of evolution is the use of <code>"use strict";</code> developers can opt-in. But we know opt-out is better, so ES6 modules are strict by default.</p>
<p>The cycle of evolution is: a) study the text then b) interpret the text, next a) study the text, and b) interpret the text. Repeat A then B until the delta between the two (study/interpret) becomes small enough to ship. As developers we like to move fast and break things. The loop we work in is: build, ship, evaluate, repeat while it's all good. When talking about standards we need to add a step, "don't break the web". So the cycle becomes: build, ship, evaluate, oh, don't break the web then repeat while everything is still good.</p>
<p>The aim of <a href="proxy.php?url=http://extensiblewebmanifesto.org" target="_blank" rel="noopener noreferrer">the Extensible Web</a> is to tighten the feedback loop between the editors of web standards and web developers. It's critical for developers to be involved with browser vendors during this cycle. That's why we try new thinks and sometimes there's some breakage until those deltas become closer.</p>
<p>The key story of The Extensible Web is:</p>
<ul>
<li>Add missing primitives</li>
<li>Enable userland polyfills & compilers</li>
<li>Work together to design the future</li>
</ul>
<p>One think I noticed about the Ember community was the adoption of ES6 modules. I've seen some breakage in Ember Canary while this shift has been happening. It actually bothered me a bit I was thinking aren't there bigger fish to fry, like data persistence? After this talk I see how these minor growing pains do contribute to the development community as a whole. We've been hungry for modules and they've just about landed in browserland for new we're using a transpiler to AMD but this exercise not only shows browser vendors the value of modules it gives us a chance to put the concepts into flight as the standard is adopted.</p>
<h2>Just Stop</h2>
<p>This topic was introduced at the onset of the conference; and I did already touch on it once; I'd like to emphasize the importance of the community. The core team of the Ember community came to an agreement about what rules we should adopt as a development community. </p>
<p>In practice... if someone says something that offends you, do speak up for yourself by saying "Just stop." Likewise, if a friend says something that you think might offend another friend, we're all friends by the way, in case you didn't know, speak up for your friend by saying "Just stop."</p>
<p><em>Simple isn't it.</em></p>
<p>Rules were made to be broken right, well we're all human and I think that is the point. We are all capable of saying something we might regret so when we adopt to play by this rule we can help our community flourish and also benefit from the results of a thriving development community that is basically re-inventing the Web platform.</p>
<p>Whether you are a religious Jew, someone who doesn't drink, are born of a minority race, or you belong to any facet that composes a diverse community of people having a varying (even conflicting) set of world views; you can show respect toward others, so can I. <em>I really like this concept of thinking about others first.</em> I think that this idea is already engrained within the Ember community. Just think about how much sacrifice has already been poured into the framework that we use; and together iterate and improve upon.</p>
<h2>Post Hoc (After the Buzz Settles)</h2>
<p>The Ember community is more than a group of people working on a framework; the community is composed of professionals, hackers, entire families (contributors are leased by their loved ones to the community). We're not born of the same country, same blood or race, not in the same timezone; nor do we have the same native language. But we all write code for the Web platform. We have a common goal to make the Web a better platform and we use Ember.js to do it.</p>
<p><strong>Ciao, my extended Ember family, talk to you on IRC.</strong> Please do comment on this post if you'd like to share your thoughts.</p>
<h3>Links</h3>
<p>Thanks to <a href="proxy.php?url=http://us4.campaign-archive1.com/?u=ac25c8565ec37f9299ac75ca0&id=3901835468&e=bc026993ad" target="_blank" rel="noopener noreferrer">Ember Weekly Issue 51</a> for collecting the buzz on EmberConf 2014...</p>
<p><strong>Other EmberConf 2014 Notes/Summaries</strong></p>
<ul>
<li><a href="proxy.php?url=http://reefpoints.dockyard.com/2014/03/17/emberconf-picks-ups-where-the-rails-community-left-off.html" target="_blank" rel="noopener noreferrer">EmberConf picks up where the Rails community left off</a></li>
<li><a href="proxy.php?url=https://github.com/zurt/notes/blob/master/EmberConf-2014.markdown" target="_blank" rel="noopener noreferrer">EmberConf Notes (markdown), Kurt MacDonald</a></li>
<li><a href="proxy.php?url=http://chantastic.io/emberconf2014/" target="_blank" rel="noopener noreferrer">EmberConf Notes (sketched), Michael Chan</a></li>
<li><a href="proxy.php?url=http://allisonsherenmcmillan.blogspot.ca/search/label/emberconf%202014" target="_blank" rel="noopener noreferrer">EmberConf Notes (blog), Allison Sheren</a></li>
<li><a href="proxy.php?url=http://hermanradtke.com/2014/03/27/emberconf-2014.html" target="_blank" rel="noopener noreferrer">EmberConf 2014, Herman Radtke</a></li>
<li><a href="proxy.php?url=http://www.justinball.com/2014/03/27/ember-conf-2014-wrap-up/" target="_blank" rel="noopener noreferrer">EmberConf Wrap Up by Justin Ball</a></li>
</ul>
<p><strong><a href="proxy.php?url=http://emberconf.com/schedule.html" target="_blank" rel="noopener noreferrer">EmberConf 2014</a> Presentations</strong></p>
<p><strong>Day 1</strong></p>
<ul>
<li><a href="proxy.php?url=https://speakerdeck.com/tomdale/emberconf-2014-keynote" target="_blank" rel="noopener noreferrer">EmberConf 2014 Keynote - Tom Dale & Yehuda Katz</a></li>
<li><a href="proxy.php?url=https://speakerdeck.com/simplereach/using-ember-to-make-the-seemingly-impossible-easy" target="_blank" rel="noopener noreferrer">Using Ember to Make the Seemingly Impossible Easy - Heyjin Kim & Andre Malan</a></li>
<li><a href="proxy.php?url=https://speakerdeck.com/rwjblue/contributing-to-ember" target="_blank" rel="noopener noreferrer">Contributing to Ember: The Inside Scoop - Robert Jackson</a></li>
<li><a href="proxy.php?url=http://terzicigor.com/talks/index.html#/" target="_blank" rel="noopener noreferrer">Ember Data and the Way Forward - Igor Terzic</a></li>
<li><a href="proxy.php?url=http://www.slideshare.net/jo_liss/broccoli-32911567" target="_blank" rel="noopener noreferrer">No more grunt watch: Modern build workflows with Broccoli - Jo Liss</a></li>
<li><a href="proxy.php?url=http://ef4.github.io/ember-animation-demo/#/title-slide" target="_blank" rel="noopener noreferrer">Animations and Transitions in an Ember App - Edward Faulkner</a></li>
<li><a href="proxy.php?url=http://johnkpaul.github.io/presentations/emberconf/components-transclude-directives/" target="_blank" rel="noopener noreferrer">Ember Components Transclude My Directives - John K. Paul</a></li>
<li><a href="proxy.php?url=http://emberconf.com/images/slides/2014_jmack.pdf" target="_blank" rel="noopener noreferrer">Modeling the App Store and iTunes with Ember Data - Jeremy Mack</a></li>
<li><a href="proxy.php?url=http://talks.erikbryn.com/htmlbars-emberconf/" target="_blank" rel="noopener noreferrer">HTMLBars: The Next-Generation of Templating in Ember.js - Erik Bryn & Kris Selden</a></li>
</ul>
<p><strong>Day 2</strong></p>
<ul>
<li><a href="proxy.php?url=https://github.com/rpflorence/talk-emberconf-2014" target="_blank" rel="noopener noreferrer">The {{x-foo}} in You - Ryan Florence</a></li>
<li><a href="proxy.php?url=http://static.iamstef.net/ember_conf_2014.pdf" target="_blank" rel="noopener noreferrer">Ember CLI - Stef Penner</a></li>
<li><a href="proxy.php?url=https://speakerdeck.com/devarispbrown/ember-is-for-the-children" target="_blank" rel="noopener noreferrer">Ember is for the Children - DeVaris Brown</a></li>
<li><a href="proxy.php?url=https://speakerdeck.com/machty/emberconf-2014-mr-router-embraces-the-controller-alex-matchneer" target="_blank" rel="noopener noreferrer">Mr. Router embraces the Controller - Alex Matchneer</a></li>
<li><a href="proxy.php?url=https://speakerdeck.com/cmeiklejohn/divergent" target="_blank" rel="noopener noreferrer">Convergent/Divergent - Christopher Meiklejohn</a></li>
<li><a href="proxy.php?url=https://speakerdeck.com/coderberry/the-unofficial-official-ember-testing-guide" target="_blank" rel="noopener noreferrer">The Unofficial, Official Ember Testing Guide - Eric Berry</a></li>
<li><a href="proxy.php?url=https://www.dropbox.com/s/02peoxevqwjz1bu/Controlling%20Route%20Traversal.pdf" target="_blank" rel="noopener noreferrer">Controlling Route Traversal with Flows - Nathan Hammond</a></li>
<li><a href="proxy.php?url=http://madhatted.com/2014/3/28/via-emberconf-performance-in-ember-apps" target="_blank" rel="noopener noreferrer">Snappy Means Happy: Performance in Ember Apps - Matthew Beale</a></li>
</ul>