Everything tagged ui (15 posts)

Using Server Actions with Next JS

React and Next.js introduced Server Actions a while back, as a new/old way to call server-side code from the client. In this post, I'll explain what Server Actions are, how they work, and how you can use them in your Next.js applications. We'll look at why they are and are not APIs, why they can make your front end code cleaner, and why they can make your backend code messier.

Everything old is new again

In the beginning, there were <form>s. They had an action, and a method, and when you clicked the submit button, the browser would send a request to the server. The server would then process the request and send back a response, which could be a redirect. The action was the URL of the server endpoint, and the method was usually either GET or POST.

<form action="/submit" method="POST">
<input type="text" name="name" />
<button type="submit">Submit</button>
</form>
<form action="/submit" method="POST">
<input type="text" name="name" />
<button type="submit">Submit</button>
</form>

Then came AJAX, and suddenly we could send requests to the server without reloading the page. This was a game-changer, and it opened up a whole new world of possibilities for building web applications. But it also introduced a lot of complexity, as developers had to manage things like network requests, error handling, and loading states. We ended up building React components like this:

TheOldWay.jsx
//this is just so 2019
export default function CreateDevice() {
const [name, setName] = useState('');
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);

const handleSubmit = async (e) => {
e.preventDefault();
setLoading(true);
try {
await fetch('/api/devices', {
method: 'POST',
body: JSON.stringify({ name }),
headers: {
'Content-Type': 'application/json',
},
});
} catch (err) {
setError(err);
} finally {
setLoading(false);
}
};

return (
<form onSubmit={handleSubmit}>
<input type="text" value={name} onChange={(e) => setName(e.target.value)} />
<button type="submit" disabled={loading}>Submit</button>
{error && <p>{error.message}</p>}
</form>
);
}
TheOldWay.jsx
//this is just so 2019
export default function CreateDevice() {
const [name, setName] = useState('');
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);

const handleSubmit = async (e) => {
e.preventDefault();
setLoading(true);
try {
await fetch('/api/devices', {
method: 'POST',
body: JSON.stringify({ name }),
headers: {
'Content-Type': 'application/json',
},
});
} catch (err) {
setError(err);
} finally {
setLoading(false);
}
};

return (
<form onSubmit={handleSubmit}>
<input type="text" value={name} onChange={(e) => setName(e.target.value)} />
<button type="submit" disabled={loading}>Submit</button>
{error && <p>{error.message}</p>}
</form>
);
}

This code is fine, but it's a lot of boilerplate for something as simple as submitting a form. It's also not very readable, as the logic for handling the form submission is mixed in with the UI code. Wouldn't it be nice if we could go back to the good old days of <form>s, but without the page reload?

Enter Server Actions

Now, with Server Actions, React is bringing back the simplicity of the old days, while still taking advantage of the power of modern web technologies. Server Actions allow you to call server-side code from the client, just like you would with a traditional form submission, but without the page reload. It wants you to think that this is all happening without an API on the backend, but this isn't true. It's not magic after all.

Here's how we can write the same form using Server Actions:

app/components/AddDeviceForm.tsx
'use client';
import { useFormState } from 'react-dom';
import { createDeviceAction } from '@/app/actions/devices';

export function AddDeviceForm() {
const [state, formAction] = useFormState(createDeviceAction, {});

return (
<form action={formAction} className="create-device">
<fieldset>
<label htmlFor="name">Name:</label>
<input type="text" name="name" id="name" placeholder="type something" />
<button type="submit">Submit</button>
</fieldset>
{state.status === 'error' && <p className="text-red-500">{state.message}</p>}
{state.status === 'success' && <p className="text-green-500">{state.message}</p>}
</form>
);
}
app/components/AddDeviceForm.tsx
'use client';
import { useFormState } from 'react-dom';
import { createDeviceAction } from '@/app/actions/devices';

export function AddDeviceForm() {
const [state, formAction] = useFormState(createDeviceAction, {});

return (
<form action={formAction} className="create-device">
<fieldset>
<label htmlFor="name">Name:</label>
<input type="text" name="name" id="name" placeholder="type something" />
<button type="submit">Submit</button>
</fieldset>
{state.status === 'error' && <p className="text-red-500">{state.message}</p>}
{state.status === 'success' && <p className="text-green-500">{state.message}</p>}
</form>
);
}

Here's that same AddDeviceForm Component running live in this page. It's a real React component, so try submitting it with and without text in the input field. In both cases it's hitting our createDeviceAction function, which is just a simple function that returns a success or error message based on the input:

One nice thing about this is that the Enter key works on your keyboard without any extra code. This is because the form is a real form, and the submit button is a real submit button. The formAction hook is doing the work of intercepting the form submission and calling the server action instead of the default form submission. It feels more like the old school web.

And here's the actual server action that is being called, in a file called app/actions/devices.ts:

app/actions/devices.ts
'use server';

export async function createDeviceAction(prevState: any, formData: FormData) {
const name = formData.get('name');

if (name) {
const device = {
name,
id: Math.round(Math.random() * 10000),
};

return {
status: 'success',
message: `Device '${name}' created with ID: ${device.id}`,
device,
};
} else {
return {
status: 'error',
message: 'Name is required',
};
}
}
app/actions/devices.ts
'use server';

export async function createDeviceAction(prevState: any, formData: FormData) {
const name = formData.get('name');

if (name) {
const device = {
name,
id: Math.round(Math.random() * 10000),
};

return {
status: 'success',
message: `Device '${name}' created with ID: ${device.id}`,
device,
};
} else {
return {
status: 'error',
message: 'Name is required',
};
}
}

The code here is simulating a database mutation and doing some basic validation. This all ought to look pretty familiar. Again, this is the actual copy/pasted code actually running behind the scenes.

How does this work?

We didn't set up any API routes, we didn't write any network request code, and we didn't have to handle any loading states or error handling. There is no code I am not showing you, stitching things together. We just wrote a simple form, and the Server Actions library took care of the rest. It's like magic!

But it's not magic. It's HTTP. If you open up your browser's developer tools and submit the form, you'll see a network request being made to the server, just like with a traditional form submission. The only difference is that the request is being intercepted by the Server Actions library and handled by the createDeviceAction function instead of the default form submission handler. This results in a POST request being sent to the current URL, with the form data and a bunch of other stuff being sent along with it.

Form submission network request
The network request that our form made. The actual data we sent is in the 1_name key

Here's what the response looked like:

Form submission network response
We got our data back, plus some other stuff Next.js sends

Next.js has basically created an API endpoint for us, and then provided its own wrapper calls and data structures on both the request and response cycles, leaving us to focus solely on our UI and business logic.

Visual feedback for slower requests

In many cases, the backend may take a few seconds to process the user's request. It's always a good idea to provide some visual feedback to the user while they are waiting. There's another lovely new React hook called useFormStatus that we can use to show a loading spinner while the request is pending. Here's a slightly modified version of the form that shows gives the user some feedback while the request is being processed:

app/components/AddDeviceFormSlow.tsx
'use client';
import { useFormState, useFormStatus } from 'react-dom';
import { createDeviceActionSlow } from '@/app/actions/devices';

export function AddDeviceFormSlow() {
const [state, formAction] = useFormState(createDeviceActionSlow, {});

return (
<form action={formAction} className="create-device">
<fieldset>
<label htmlFor="name">Name:</label>
<input type="text" name="name" id="name" placeholder="type something" />
<SubmitButton />
</fieldset>
{state.status === 'error' && <p className="text-red-500">{state.message}</p>}
{state.status === 'success' && <p className="text-green-500">{state.message}</p>}
</form>
);
}

//this has to be a separate component because we can't use the useFormStatus hook in the
//same component that has the <form>. Sadface.
function SubmitButton() {
const { pending } = useFormStatus();

return (
<button type="submit" disabled={pending}>
{pending ? 'Submitting...' : 'Submit'}
</button>
);
}
app/components/AddDeviceFormSlow.tsx
'use client';
import { useFormState, useFormStatus } from 'react-dom';
import { createDeviceActionSlow } from '@/app/actions/devices';

export function AddDeviceFormSlow() {
const [state, formAction] = useFormState(createDeviceActionSlow, {});

return (
<form action={formAction} className="create-device">
<fieldset>
<label htmlFor="name">Name:</label>
<input type="text" name="name" id="name" placeholder="type something" />
<SubmitButton />
</fieldset>
{state.status === 'error' && <p className="text-red-500">{state.message}</p>}
{state.status === 'success' && <p className="text-green-500">{state.message}</p>}
</form>
);
}

//this has to be a separate component because we can't use the useFormStatus hook in the
//same component that has the <form>. Sadface.
function SubmitButton() {
const { pending } = useFormStatus();

return (
<button type="submit" disabled={pending}>
{pending ? 'Submitting...' : 'Submit'}
</button>
);
}

This is almost identical to the first example, but I've split the submit button into a separate component and used the useFormStatus hook to show a loading spinner when the request is pending. It's also now pointing at the createDeviceActionSlow function, which is identical to the createDeviceAction function except it has a 3 second delay before returning the response.

Here's the live component - give it a whirl:

That's pretty cool. The useFormStatus hook is doing all the work of tracking the request status and updating the UI accordingly. It's a small thing, but it makes both the user experience and the developer experience a lot better.

What about the API?

It has been the case for quite some time that the greatest value in a web application is often not found in its UI but in its API. The UI is just a way to interact with the API, and the API is where the real work gets done. If your application is genuinely useful to other people, there's a good chance they will want to integrate with it via an API.

There is a school of thought that says your UI should be treated just the same as any other API client for your system. This is a good school, and its teachers are worth listening to. UIs are for humans and APIs are for machines, but there's a lot of overlap in what they want in life:

  • A speedy response
  • To know if their action succeeded, or why it failed
  • To get the data they asked for, in a format they can easily consume

Can't we service them both with the same code? Yes, we can. But it's not always as simple as it seems.

The real world spoils the fun

Way up in that second example snippet, we were making a POST request to /api/devices; our UI code was talking to the exact same API endpoint that any other API user would be talking to. There are many obvious benefits to this, mostly centering around the fact that you don't need to maintain parallel code paths for UI and API users. I've worked on systems that did that, and it can end up doubling your codebase.

Server Actions are great, but they take us away from HTTP and REST, which are bedrock technologies for APIs. It's very easy to spam together a bunch of Server Actions for your UI, and then find yourself in a mess when you need to build an API for someone else to use.

The reality is that although API users and UI users do have a lot in common, they also have differences. In our Server Action examples above we were returning a simple object with a status and a message, but in a real API you would likely want to return a more structured response, with an HTTP status code, headers, and a body. We're also much more likely to need things like rate limiting for our API users, which we didn't have to think about for our UI users.

Consider a super simple POST endpoint in a real API. Assume you're using Prisma and Zod for validation - a fairly common pairing. Here's how you might write that API endpoint:

app/api/devices/route.ts
export async function POST(req: NextRequest) {
try {
const body = await req.json();

const data = {
type: body.type,
hostname: body.hostname,
credentials: body.credentials,
} as Prisma.DeviceCreateInput;

DeviceSchema.parse(data);
const device = prisma.device.create({ data });

return NextResponse.json(device, { status: 201 });
} catch (error) {
if (error instanceof ZodError) {
return NextResponse.json({ error: { issues: error.issues } }, { status: 400 });
}
return NextResponse.json({ error: "Failed to create device" }, { status: 500 });
}
}
app/api/devices/route.ts
export async function POST(req: NextRequest) {
try {
const body = await req.json();

const data = {
type: body.type,
hostname: body.hostname,
credentials: body.credentials,
} as Prisma.DeviceCreateInput;

DeviceSchema.parse(data);
const device = prisma.device.create({ data });

return NextResponse.json(device, { status: 201 });
} catch (error) {
if (error instanceof ZodError) {
return NextResponse.json({ error: { issues: error.issues } }, { status: 400 });
}
return NextResponse.json({ error: "Failed to create device" }, { status: 500 });
}
}

This API endpoint consumes JSON input (assume that auth is handled via middleware), validates it with Zod, and then creates a new device in the database. If the input is invalid, it returns a 400 status code with an error message. If the input looks good but there's an error creating the device, it returns a 500 status code with an error message. If everything goes well, it returns a 201 status code with the newly created device.

Now let's see how we might write a Server Action for the same functionality:

app/actions/devices.ts
'use server';

export async function createDeviceAction(prevState: any, formData: FormData) {
try {
const data = {
type: formData.get("type"),
hostname: formData.get("hostname"),
credentials: formData.get("credentials"),
} as Prisma.DeviceCreateInput;

DeviceSchema.parse(data);
const device = prisma.device.create({ data });

revalidatePath("/devices");

return {
success: true,
message: "Device Created Successfully",
device,
};
} catch (error) {
if (error instanceof ZodError) {
return {
success: false,
message: "Validation Error",
error: {
issues: error.issues,
},
};
}

return {
success: false,
message: "Failed to create device",
error: JSON.stringify(error),
};
}
}
app/actions/devices.ts
'use server';

export async function createDeviceAction(prevState: any, formData: FormData) {
try {
const data = {
type: formData.get("type"),
hostname: formData.get("hostname"),
credentials: formData.get("credentials"),
} as Prisma.DeviceCreateInput;

DeviceSchema.parse(data);
const device = prisma.device.create({ data });

revalidatePath("/devices");

return {
success: true,
message: "Device Created Successfully",
device,
};
} catch (error) {
if (error instanceof ZodError) {
return {
success: false,
message: "Validation Error",
error: {
issues: error.issues,
},
};
}

return {
success: false,
message: "Failed to create device",
error: JSON.stringify(error),
};
}
}

The core of these 2 functions is the same exact 2 lines - one to validate using zod, the other to persist using Prisma. The flow is exactly the same, but in one case we're grabbing JSON, in the other reading form data. In one case we're returning NextResponse objects with HTTP status codes, in the other we're returning objects with success and message keys. The Server Action can also take advantage of nice things like revalidatePath to trigger a revalidation of the page that called it, but we don't want that line in our API endpoint.

Somewhere along the line we will want to show a message to the UI user telling them what happened - hence the message key in the Server Action (the API user can just read the HTTP status code). We could have moved that logic to the UI instead, perhaps returning a statusCode key in the JSON response to emulate an HTTP status code. But that's just reimplementing part of HTTP, and moving the problem to the client, which now has to provide the mapping from a status code to a message. It also means a bigger bundle if we want to support internationalization for those messages.

What this all means is that if you want to take advantage of the UI code cleanliness benefits that come from using Server Actions, and your application conceivably might need an API now or in the future, you need to think about how you are going to avoid duplicating logic between your Server Actions and your API endpoints. This may be a hard problem, and there's no one-size-fits-all solution. Yes you can pull those 2 lines of core logic out into a shared function, but you're still left with a lot of other almost-the-same-but-not-quite code.

Ultimately, it probably just requires another layer of indirection. What that layer looks like will depend on your application, but it's something to think about before you go all-in on Server Actions.

Continue reading

Sencha Con 2013 Wrapup

So another great Sencha Con is over, and I'm left to reflect on everything that went on over the last few days. This time was easily the biggest and best Sencha Con that I've been to, with 800 people in attendance and a very high bar set by the speakers. The organization was excellent, the location fun (even if the bars don't open until 5pm...), and the enthusiasm palpable.

I've made a few posts over the last few days so won't repeat the content here - if you want to see what else happened check these out too:

What I will do though is repeat my invitation to take a look at what we're doing with JavaScript at C3 Energy. I wrote up a quick post about it yesterday and would love to hear from you - whether you're at Sencha Con or not.

Now on to some general thoughts.

Content

There was a large range in the technical difficulty of the content, with perhaps a slightly stronger skew up the difficulty chain compared to previous events. This is a good thing, though there's probably still room for more advanced content. Having been there before though, I know how hard it is to pitch that right so that everyone enjoys and gets value of out it.

The biggest challenge for me was the sheer number of tracks - at any one time there would be seven talks happening simultaneously, two or three of which I'd really want to watch. Personally I'd really love it if the hackathon was dropped in favor of a third day of sessions, with a shift down to 4-5 tracks. I'm sure there's a cost implication to that, but it's worth thinking about.

Videos

There were cameras set up in at least the main hall on the first day, but I didn't see any on day 2. I did overhear that the video streams were being recorded directly from what was being shown on the projectors, with the audio recorded separately. If that's true I'd guess it would make editing a bit easier so maybe that'll means a quick release.

Naturally, take this with a pinch of salt until the official announcement comes out. In the meantime, there's at least one video available so far:

Fun Things

The community pavilion was a great idea, and served as the perfect space for attendees with hang out away from the other rascals running around the hotel. Coffee and snacks were available whenever I needed them, and there was plenty of seating to chill out in.

I missed out on the visit to the theme park, which I hear was by far the most fun part of the event. Having a theme park kick out everyone but Sencha Con attendees while serving copious amounts of alcohol seemed to go down very well with the attendees!

Continue reading

Sencha Con 2013: Ext JS Performance tips

Just as with Jacky's session, I didn't plan on making a separate post about this, but again the content was so good and I ended up taking so many notes that it also warrants its own space. To save myself from early carpal tunnel syndrome I'm going to leave this one in more of a bullet point format.

Ext JS has been getting more flexible with each release. You can do many more things with it these days than you used to be able to, but there has been a performance cost associated with that. In many cases this performance degradation is down to the way the framework is being used, as opposed to a fundamental problem with the framework itself.

There's a whole bunch of things that you can do to dramatically speed up the performance of an app you're not happy with, and Nige "Animal" White took us through them this morning. Here's what I was able to write down in time:

Slow things

Nige identified three of the top causes of sluggish apps, which we'll go through one by one:

  • Network latency
  • JS execution
  • Layout activity

Network latency:

  • Bad ux - got to stare at blank screen for a while
  • Use Sencha Command to build the app - single file, minimized
  • 4810ms vs 352ms = dynamic loading vs built

JavaScript execution:

  • Avoid slow JS engines (he says with a wry smile)
  • Optimize repeated code - for loops should be tight, cache variables outside
  • Ideally, don't do any processing at render time
  • Minimize function calls
  • Lazily instantiate items
  • Use the PageAnalyzer (in the Ext JS SDK examples folder) to benchmark your applications
  • Start Chrome with --enable-benchmarking to get much more accurate timing information out of the browser

Layouts

Suspend store events when adding/removing many records. Otherwise we're going to get a full Ext JS layout pass for each modification

grid.store.suspendEvents();
//do lots of updating
grid.store.resumeEvents();
grid.view.refresh()

grid.store.suspendEvents();
//do lots of updating
grid.store.resumeEvents();
grid.view.refresh()

Ditto on trees (they're the same as grids) Coalesce multiple layouts. If you're adding/removing a bunch of Components in a single go, do it like this:

Ext.suspendLayouts();
//do a bunch of UI updates
Ext.resumeLayouts(true);

Ext.suspendLayouts();
//do a bunch of UI updates
Ext.resumeLayouts(true);

Container#add accepts an array of items, which is faster than iterating over that array yourself and calling .add for each one. Avoid layout constraints where possible - in box layouts, align: 'stretchmax' is slow because it has to do multiple layout runs. Avoid minHeight, maxHeight, minWidth, maxWidth if possible

At startup:

  • Embed initialization data inside the HTML if possible - avoids AJAX requests
  • Configure the entire layout in one shot using that data
  • Do not make multiple Ajax requests, and build the layout in response

Use the 'idle' event

  • Similar to the AnimationQueue
  • Ext.globalEvents.on('idle', myFunction) - called once a big layout/repaint run has finished
  • Using the idle listener sometimes preferable to setTimeout(myFunction, 1), because it's synchronous in the same repaint cycle. The setTimeout approach means the repaint happens, then your code is called. If your code itself requires a repaint, that means you'll have 2 repaints in setTimeout vs 1 in on.('idle')

Reduce layout depth

Big problem - overnesting. People very often do this with grids:

{
xtype: 'tabpanel',
items: [
{
title: 'Results',
items: {
xtype: 'grid'
}
}
]
}
{
xtype: 'tabpanel',
items: [
{
title: 'Results',
items: {
xtype: 'grid'
}
}
]
}

Better:

{
xtype: 'tabpanel',
items: {
title: 'Results',
xtype: 'grid'
}
}
{
xtype: 'tabpanel',
items: {
title: 'Results',
xtype: 'grid'
}
}

This is important because redundant components still cost CPU and memory. Everything is a Component now - panel headers, icons, etc etc. Can be constructing more Components than you realize. Much more flexible, but easy to abuse

Lazy Instantiation

New plugin at https://gist.github.com/ExtAnimal/c93148f5194f2a232464

{
xtype: 'tabpanel',
ptype: 'lazyitems',
items: {
title: 'Results',
xtype: 'grid'
}
}
{
xtype: 'tabpanel',
ptype: 'lazyitems',
items: {
title: 'Results',
xtype: 'grid'
}
}

Overall impact

On a real life large example contributed by a Sencha customer:

Bad practices: 5187ms (IE8) Good practices: 1813ms (IE8) 1300ms vs 550ms on Chrome (same example)

Colossal impact on the Ext.suspendLayout example - 4700ms vs 100ms on Chrome

Summary

This is definitely a talk you'll want to watch when they go online. It was absolutely brimming with content and the advice comes straight from the horse's mouth. Nige did a great job presenting, and reminded us that performance is a shared responsibility - the framework is getting faster as time goes by, but we the developers need to do our share too to make sure it stays fast.

Continue reading

Sencha Con 2013: Fastbook

I didn't plan on writing a post purely on Fastbook, but Jacky's presentation just now was so good I felt it needed one. If you haven't seen Fastbook yet, it is Sencha's answer to the (over reported) comments by Zuckerburg that using HTML5 for Facebook's mobile app was a mistake.

After those comments there was a lot of debate around whether HTML5 is ready for the big time. Plenty of opinions were thrown around, but not all based on evidence. Jacky was curious about why Facebook's old app was so slow, and wondered if he could use the same technologies to achieve a much better result. To say he was successful would be a spectacular understatement - Fastbook absolutely flies.

Performance can be hard to describe in words, so Sencha released this video that demonstrates the HTML5 Fastbook app against the new native Facebook apps. As you can see, not only is the HTML5 version at least as fast and fluid as the native versions, in several cases it's actually significantly better (especially on Android).

Fastbook

Challenges

The biggest challenge here is dynamically loading and scrolling large quantities of data while presenting a 60fps experience to the user. 60fps means you have just 16.7ms per frame to do everything, which is a hugely tall order on a CPU and memory constrained mobile device.

The way to achieve this is to treat the page as an app rather than a traditional web page. This means we need to be a lot more proactive in managing how and when things are rendered - something that traditionally has been in the domain of the browser's own rendering and layout engines. Thankfully, the framework will do all of this for you.

As an example, Jacky loaded up Gmail's web app and showed what happens when you scroll a long way down your inbox. The more you scroll, the more divs are added to the document (one new div per message). Each div contains a bunch of child elements too, so we're adding maybe a dozen or so nodes to our DOM tree per message.

The problem with this is that as the DOM tree gets larger and larger, everything slows down. You could see the inspector showing slower and slower layout recalculations, making the app sluggish.

The solution is to recycle DOM nodes once they're no longer visible. In this way, a list that seems to have infinite content could contain only say 10 elements - just enough to fill the screen. Once you scroll down the list, DOM nodes that scrolled off the top are detached, updated with new data and placed at the bottom of the list. Simple. Ingenius. Beautiful.

Prioritization

There's usually a lot more going on in an app than just animating a scrolling view though. There's data to load via AJAX, images to load, compositing, processing, and whatever else your app needs to do. And then there are touch events, which need to feel perfectly responsive at all times, even while all of this is going on.

To make this sane and manageable, we have a new class called AnimationQueue. All of the jobs I just mentioned above - handling touch events, animation, network requests and so on - are dispatched through the AnimationQueue with a given priority. Touch event handling has the top priority, followed by animation, followed by everything else.

AnimationQueue does as much as it can in that 16.7ms window, then breaks execution again to allow the browser to reflow/repaint/whatever else it needs to do. What this means is that while scrolling down a large list, it's likely that our CPU/GPU is being taxed so much that we don't have any time to load images or other low priority jobs.

This is a Good Thing, because if we're scrolling through a large list there's a good chance we are going to skip right over those images anyway. In the end they're loaded as soon as the AnimationQueue has some spare time, which is normally when your scrolling of the list has slowed down or stopped.

Sandboxing

The final, and most complex technique Jacky discussed was Sandboxing. The larger your application gets, the larger the DOM tree. Even if you are using best practices, there's an expense to simply having so many components on the same page. The bottleneck here is in the browser itself - looks like we need another hack.

To get around this, we can dynamically create iframes that contain parts of our DOM tree. This way our main page DOM tree can remain small but we can still have a huge application. This not only speeds up browser repaint and reflow, it also improves compositing performance, DOM querying and more.

This all happens under the covers and Jacky's aiming on including Ext.Sandbox in Sencha Touch 2.3 so that all apps can take advantage of this huge improvement. He cautioned (rightly) that it'll only make 2.3 if it's up to his high standards though, so watch this space.

Continue reading

Sencha Con 2013 Day 1

Sencha Con 2013 kicked off today, with some stunning improvements demoed across the product set. I'm attending as an audience member for the first time so thought I'd share how things look from the cheap seats.

Keynote

The keynote was very well put together, with none of the AV issues that plagued us last year (maybe they seemed worse from behind the curtain!). It started off with a welcome from Paul Kopacki, followed by some insights into the current status of developers in the world of business (apparently we're kingmakers - who knew!). One of Blackberry's evangelists came up and made a pretty good pitch for giving them a second look (the free hardware probably helped a little...)

The meat, though, was in the second half of the presentation. We were treated to a succession of great new features across Ext JS, Sencha Touch and Sencha Architect, which I'll go into in a little more detail below.

But it was Abe Elias and Jacky Nguyen who stole the show in the end. Unleashing a visionary new product, Sencha Space, they demonstrated a brand new way to enable businesses to elegantly solve the problem of BYOD (Bring Your Own Device).

Nobody wants to be given a mobile phone by their IT department when they've got a brand new iPhone in their pocket. But those IT guys have good reason for doing this - consumer browsers are currently inherently insecure. Sencha Space solves this problem by providing a single app that employees can install, log in to and gain access to all of the apps needed to be productive in the company.

I could write a lot more about it but the 2 minute video below can surely do a better job:

Update: looks like this video got taken down at some point

Ext JS upgrades

The keynote lasted most of the morning, but in the afternoon Don Griffin came back on stage to tell us more about what's coming soon in Ext JS. Don heads up Ext JS these days, and is one of the most intelligent and experienced people I've had the joy of working with. I'm pretty sure he gained the largest amount of spontaneous applause of the day during the Ext JS talk, which is no surprise given the awesome stuff he showed us.

I forget which order things were revealed in, but these things stood out for me:

  • Touch Support - while this may seem anathema to the thinking behind Ext JS, it's an undeniable fact that people try to use Ext JS applications on tablets. Whether they should or not is a different question, but in this next release it will be officially supported by the framework. Momentum scrolling, pinch to zoom and dragdrop resizing are all supported at your fingertips.
  • Grid Gadgets - quite likely the coolest new feature, Gadgets allow you to render any Component into each cell in a Grid, in an extremely CPU and memory efficient manner. Seeing a live grid updating with rich charts and other widgets at high frequency was a fantastic experience
  • Border Layout - allows your users to rearrange the border layouts used in your apps with drag and drop. Easy to switch between accordion layout, box layout or tabs
  • A shedload more. The enforced pub crawl has temporarily relieved me of a full memory. So impressed with everything that was demonstrated today.

Sencha Touch upgrades

Jacky came up and delivered a presentation on what's coming up in Sencha Touch, using his idiosyncratic and inimitable style. Some of the things that stood out for me:

  • Touch gets a grid. It performs really well and looks great. Good for (sparing) use on tablet apps
  • XML configs. Not sure how I feel about this yet, but ST 2.3 will allow for views to be declared in XML, which is transformed into the normal JSON format under the covers. You end up writing few lines of code, but the overall file size probably doesn't change too much. With a decent editor the syntax highlighting definitely makes the View code easier to read though
  • ViewModel. Just as we have Ext.data.Model for encapsulating data models, we now have ViewModel for encapsulating a view model, which includes things like state. Leads to a much improved API for updating Views in response to other changes
  • Theming. 2 additional themes were added, and the others have all been refactored to make theming even easier

Again there's a lot more here and I couldn't possibly do it all justice in a blog post. It's geniunely thrilling to see these young frameworks mature into stellar products that are being used by literally millions of developers. Very exciting.

Architect upgrades

Architect has come a really long way since its inception a couple of years ago. The new features introduced today looked like some of the largest steps forward the product has ever taken. I'm finally getting close to actually thinking about using it in real life (I'm a glutten for editing code in Sublime Text). Some standout features:

  • New template apps to get you up and running with a new app in seconds
  • Integration with Appurify, which allows you to test your Architect apps on real devices hosted by their service
  • Allows you to install third party extensions into Architect, and have them seamlessly integrated into your project

Day 1 Summary

Although I worked with these people for years, somehow I'm still surprised when I see every single developer giving world class presentations. I don't know how I was able to leave Sencha a year ago, but every time I interact with Abe, Don, Jacky, Tommy, Jamie, Rob, Nige, and all of the other rockstars at that place I'm reminded what a great and unique time that was. Really looking forward to what tomorrow brings!

Continue reading

Anatomy of a Sencha Touch 2 App

At its simplest, a Sencha Touch 2 application is just a small collection of text files - html, css and javascript. But applications often grow over time so to keep things organized and maintainable we have a set of simple conventions around how to structure and manage your application's code.

A little while back we introduced a technology called Sencha Command. Command got a big overhaul for 2.0 and today it can generate all of the files your application needs for you. To get Sencha Command you'll need to install the SDK Tools and then open up your terminal. To run the app generator you'll need to make sure you've got a copy of the Sencha Touch 2 SDK, cd into it in your terminal and run the app generate command:

sencha generate app MyApp ../MyApp
sencha generate app MyApp ../MyApp

This creates an application called MyApp with all of the files and folders you'll need to get started generated for you. You end up with a folder structure that looks like this:

Sencha Touch 2 Directory Overview

This looks like a fair number of files and folders because I've expanded the app folder in the image above but really there are only 4 files and 3 folders at the top level. Let's look at the files first:

  • index.html: simplest HTML file ever, just includes the app JS and CSS, plus a loading spinner
  • app.js: this is the heart of your app, sets up app name, dependencies and a launch function
  • app.json: used by the microloader to cache your app files in localStorage so it boots up faster
  • packager.json: configuration file used to package your app for native app stores

To begin with you'll only really need to edit app.js - the others come in useful later on. Now let's take a look at the folders:

  • app: contains all of your application's source files - models, views, controllers etc
  • resources: contains the images and CSS used by your app, including the source SASS files
  • sdk: contains the important parts of the Touch SDK, including Sencha Command

The app folder

You'll spend 90%+ of your time inside the app folder, so let's drill down and take a look at what's inside that. We've got 5 subfolders, all of which are empty except one - the view folder. This just contains a template view file that renders a tab panel when you first boot the app up. Let's look at each:

The resources folder

Moving on, let's take a look at the resources folder:

Sencha Touch 2 resources folder

Five folders this time - in turn:

  • icons: the set of icons used when your app is added to the home screen. We create some nice default ones for you
  • loading: the loading/startup screen images to use when your app's on a home screen or natively packaged
  • images: this is where you should put any app images that are not icons or loading images
  • sass: the source SASS files for your app. This is the place to alter the theming for your app, remove any CSS you're not using and add your own styles
  • css: the compiled SASS files - these are the CSS files your app will use in production and are automatically minified for you

There are quite a few icon and loading images needed to cover all of the different sizes and resolutions of the devices that Sencha Touch 2 supports. We've included all of the different formats with the conventional file names as a guide - you can just replace the contents of resources/icons and resources/loading with your own images.

The sdk folder

Finally there's the SDK directory, which contains the SDK's source code and all of the dependencies used by Sencha Command. This includes Node.js, Phantom JS and others so it can start to add up. Of course, none of this goes into your production builds, which we keep as tiny and fast-loading as possible, but if you're not going to use the SDK Tools (bad move, but your call!) you can remove the sdk/command directory to keep things leaner.

By vendoring all third-party dependencies like Node.js into your application directory we can be confident that there are no system-specific dependencies required, so you can zip up your app, send it to a friend and so long as she has the SDK Tools installed, everything should just work.

Hopefully that lays out the large-scale structure of what goes where and why - feel free to ask questions!

Continue reading

Building a data-driven image carousel with Sencha Touch 2

This evening I embarked on a little stellar voyage that I'd like to share with you all. Most people with great taste love astronomy and Sencha Touch 2, so why not combine them in a fun evening's web app building?

NASA has been running a small site called APOD (Astronomy Picture Of the Day) for a long time now, as you can probably tell by the awesome web design of that page. Despite its 1998-era styling, this site incorporates some pretty stunning images of the universe and is begging for a mobile app interpretation.

We're not going to go crazy, in fact this whole thing only took about an hour to create, but hopefully it's a useful look at how to put something like this together. In this case, we're just going to write a quick app that pulls down the last 20 pictures and shows them in a carousel with an optional title.

Here's what it looks like live. You'll need a webkit browser (Chrome or Safari) to see this, alternatively load up http://code.edspencer.net/apod on a phone or tablet device:

The full source code for the app is up on github, and we'll go through it bit by bit below.

The App

Our app consists of 5 files:

  • index.html, which includes our JavaScript files and a little CSS
  • app.js, which boots our application up
  • app/model/Picture.js, which represents a single APOD picture
  • app/view/Picture.js, which shows a picture on the page
  • app/store/Pictures.js, which fetches the pictures from the APOD RSS feed

The whole thing is up on github and you can see a live demo at http://code.edspencer.net/apod. To see what it's doing tap that link on your phone or tablet, and to really feel it add it to your homescreen to get rid of that browser chrome.

The Code

Most of the action happens in app.js, which for your enjoyment is more documentation than code. Here's the gist of it:

/*
* This app uses a Carousel and a JSON-P proxy so make sure they're loaded first
*/
Ext.require([
'Ext.carousel.Carousel',
'Ext.data.proxy.JsonP'
]);

/**
* Our app is pretty simple - it just grabs the latest images from NASA's Astronomy Picture Of the Day
* (http://apod.nasa.gov/apod/astropix.html) and displays them in a Carousel. This file drives most of
* the application, but there's also:
*
* * A Store - app/store/Pictures.js - that fetches the data from the APOD RSS feed
* * A Model - app/model/Picture.js - that represents a single image from the feed
* * A View - app/view/Picture.js - that displays each image
*
* Our application's launch function is called automatically when everything is loaded.
*/
Ext.application({
name: 'apod',

models: ['Picture'],
stores: ['Pictures'],
views: ['Picture'],

launch: function() {
var titleVisible = false,
info, carousel;

/**
* The main carousel that drives our app. We're just telling it to use the Pictures store and
* to update the info bar whenever a new image is swiped to
*/
carousel = Ext.create('Ext.Carousel', {
store: 'Pictures',
direction: 'horizontal',

listeners: {
activeitemchange: function(carousel, item) {
info.setHtml(item.getPicture().get('title'));
}
}
});

/**
* This is just a reusable Component that we pin to the top of the page. This is hidden by default
* and appears when the user taps on the screen. The activeitemchange listener above updates the
* content of this Component whenever a new image is swiped to
*/
info = Ext.create('Ext.Component', {
cls: 'apod-title',
top: 0,
left: 0,
right: 0
});

//add both of our views to the Viewport so they're rendered and visible
Ext.Viewport.add(carousel);
Ext.Viewport.add(info);

/**
* The Pictures store (see app/store/Pictures.js) is set to not load automatically, so we load it
* manually now. This loads data from the APOD RSS feed and calls our callback function once it's
* loaded.
*
* All we do here is iterate over all of the data, creating an apodimage Component for each item.
* Then we just add those items to the Carousel and set the first item active.
*/
Ext.getStore('Pictures').load(function(pictures) {
var items = [];

Ext.each(pictures, function(picture) {
if (!picture.get('image')) {
return;
}

items.push({
xtype: 'apodimage',
picture: picture
});
});

carousel.setItems(items);
carousel.setActiveItem(0);
});

/**
* The final thing is to add a tap listener that is called whenever the user taps on the screen.
* We do a quick check to make sure they're not tapping on the carousel indicators (tapping on
* those indicators moves you between items so we don't want to override that), then either hide
* or show the info Component.
*
* Note that to hide or show this Component we're adding or removing the apod-title-visible class.
* If you look at index.html you'll see the CSS rules style the info bar and also cause it to fade
* in and out when you tap.
*/
Ext.Viewport.element.on('tap', function(e) {
if (!e.getTarget('.x-carousel-indicator')) {
if (titleVisible) {
info.element.removeCls('apod-title-visible');
titleVisible = false;
} else {
info.element.addCls('apod-title-visible');
titleVisible = true;
}
}
});
}
});
/*
* This app uses a Carousel and a JSON-P proxy so make sure they're loaded first
*/
Ext.require([
'Ext.carousel.Carousel',
'Ext.data.proxy.JsonP'
]);

/**
* Our app is pretty simple - it just grabs the latest images from NASA's Astronomy Picture Of the Day
* (http://apod.nasa.gov/apod/astropix.html) and displays them in a Carousel. This file drives most of
* the application, but there's also:
*
* * A Store - app/store/Pictures.js - that fetches the data from the APOD RSS feed
* * A Model - app/model/Picture.js - that represents a single image from the feed
* * A View - app/view/Picture.js - that displays each image
*
* Our application's launch function is called automatically when everything is loaded.
*/
Ext.application({
name: 'apod',

models: ['Picture'],
stores: ['Pictures'],
views: ['Picture'],

launch: function() {
var titleVisible = false,
info, carousel;

/**
* The main carousel that drives our app. We're just telling it to use the Pictures store and
* to update the info bar whenever a new image is swiped to
*/
carousel = Ext.create('Ext.Carousel', {
store: 'Pictures',
direction: 'horizontal',

listeners: {
activeitemchange: function(carousel, item) {
info.setHtml(item.getPicture().get('title'));
}
}
});

/**
* This is just a reusable Component that we pin to the top of the page. This is hidden by default
* and appears when the user taps on the screen. The activeitemchange listener above updates the
* content of this Component whenever a new image is swiped to
*/
info = Ext.create('Ext.Component', {
cls: 'apod-title',
top: 0,
left: 0,
right: 0
});

//add both of our views to the Viewport so they're rendered and visible
Ext.Viewport.add(carousel);
Ext.Viewport.add(info);

/**
* The Pictures store (see app/store/Pictures.js) is set to not load automatically, so we load it
* manually now. This loads data from the APOD RSS feed and calls our callback function once it's
* loaded.
*
* All we do here is iterate over all of the data, creating an apodimage Component for each item.
* Then we just add those items to the Carousel and set the first item active.
*/
Ext.getStore('Pictures').load(function(pictures) {
var items = [];

Ext.each(pictures, function(picture) {
if (!picture.get('image')) {
return;
}

items.push({
xtype: 'apodimage',
picture: picture
});
});

carousel.setItems(items);
carousel.setActiveItem(0);
});

/**
* The final thing is to add a tap listener that is called whenever the user taps on the screen.
* We do a quick check to make sure they're not tapping on the carousel indicators (tapping on
* those indicators moves you between items so we don't want to override that), then either hide
* or show the info Component.
*
* Note that to hide or show this Component we're adding or removing the apod-title-visible class.
* If you look at index.html you'll see the CSS rules style the info bar and also cause it to fade
* in and out when you tap.
*/
Ext.Viewport.element.on('tap', function(e) {
if (!e.getTarget('.x-carousel-indicator')) {
if (titleVisible) {
info.element.removeCls('apod-title-visible');
titleVisible = false;
} else {
info.element.addCls('apod-title-visible');
titleVisible = true;
}
}
});
}
});

This is pretty simple stuff and you can probably just follow the comments to see what's going on. Basically though the app.js is responsible for launching our application, creating the Carousel and info Components, and setting up a couple of convenient event listeners.

We also had a few other files:

Picture Model

Found in app/model/Picture.js, our model is mostly just a list of fields sent back in the RSS feed. There is one that's somewhat more complicated than the rest though - the 'image' field. Ideally, the RSS feed would have sent back the url of the image in a separate field and we could just pull it out like any other, but alas it is embedded inside the main content.

To get around this, we just specify a convert function that grabs the content field, finds the first image url inside of it and pulls it out. To make sure it looks good on any device we also pass it through Sencha IO src, which resizes the image to fit the screen size of whatever device we happen to be viewing it on:

/**
* Simple Model that represents an image from NASA's Astronomy Picture Of the Day. The only remarkable
* thing about this model is the 'image' field, which uses a regular expression to pull its value out
* of the main content of the RSS feed. Ideally the image url would have been presented in its own field
* in the RSS response, but as it wasn't we had to use this approach to parse it out
*/
Ext.define('apod.model.Picture', {
extend: 'Ext.data.Model',

config: {
fields: [
'id', 'title', 'link', 'author', 'content',
{
name: 'image',
type: 'string',
convert: function(value, record) {
var content = record.get('content'),
regex = /img src=\"([a-zA-Z0-9\_\.\/\:]*)\"/,
match = content.match(regex),
src = match[1];

if (src != "" && !src.match(/\.gif$/)) {
src = "http://src.sencha.io/screen.width/" + src;
}

return src;
}
}
]
}
});
/**
* Simple Model that represents an image from NASA's Astronomy Picture Of the Day. The only remarkable
* thing about this model is the 'image' field, which uses a regular expression to pull its value out
* of the main content of the RSS feed. Ideally the image url would have been presented in its own field
* in the RSS response, but as it wasn't we had to use this approach to parse it out
*/
Ext.define('apod.model.Picture', {
extend: 'Ext.data.Model',

config: {
fields: [
'id', 'title', 'link', 'author', 'content',
{
name: 'image',
type: 'string',
convert: function(value, record) {
var content = record.get('content'),
regex = /img src=\"([a-zA-Z0-9\_\.\/\:]*)\"/,
match = content.match(regex),
src = match[1];

if (src != "" && !src.match(/\.gif$/)) {
src = "http://src.sencha.io/screen.width/" + src;
}

return src;
}
}
]
}
});

Pictures Store

Our Store is even simpler than our Model. All it does is load the APOD RSS feed over JSON-P (via Google's RSS Feed API) and decode the data with a very simple JSON Reader. This automatically pulls down the images and runs them through our Model's convert function:

/**
* Grabs the APOD RSS feed from Google's Feed API, passes the data to our Model to decode
*/
Ext.define('apod.store.Pictures', {
extend: 'Ext.data.Store',

config: {
model: 'apod.model.Picture',

proxy: {
type: 'jsonp',
url: 'https://ajax.googleapis.com/ajax/services/feed/load?v=1.0&q=http://www.acme.com/jef/apod/rss.xml&num=20',

reader: {
type: 'json',
rootProperty: 'responseData.feed.entries'
}
}
}
});
/**
* Grabs the APOD RSS feed from Google's Feed API, passes the data to our Model to decode
*/
Ext.define('apod.store.Pictures', {
extend: 'Ext.data.Store',

config: {
model: 'apod.model.Picture',

proxy: {
type: 'jsonp',
url: 'https://ajax.googleapis.com/ajax/services/feed/load?v=1.0&q=http://www.acme.com/jef/apod/rss.xml&num=20',

reader: {
type: 'json',
rootProperty: 'responseData.feed.entries'
}
}
}
});

Tying it all together

Our app.js loads our Model and Store, plus a really simple Picture view that is basically just an Ext.Img. All it does then is render the Carousel and Info Component to the screen and tie up a couple of listeners.

In case you weren't paying attention before, the info component is just an Ext.Component that we rendered up in app.js as a place to render the title of the image you're currently looking at. When you swipe between items in the carousel the activeitemchange event is fired, which we listen to near the top of app.js. All our activeitemchange listener does is update the HTML of the info component to the title of the image we just swiped to.

But what about the info component itself? Well at the bottom of app.js we added a tap listener on Ext.Viewport that hides or shows the info Component whenever you tap anywhere on the screen (except if you tap on the Carousel indicator icons). With a little CSS transition loveliness we get a nice fade in/out transition when we tap the screen to reveal the image title. Here's that tap listener again:

/**
* The final thing is to add a tap listener that is called whenever the user taps on the screen.
* We do a quick check to make sure they're not tapping on the carousel indicators (tapping on
* those indicators moves you between items so we don't want to override that), then either hide
* or show the info Component.
*/
Ext.Viewport.element.on('tap', function(e) {
if (!e.getTarget('.x-carousel-indicator')) {
if (titleVisible) {
info.element.removeCls('apod-title-visible');
titleVisible = false;
} else {
info.element.addCls('apod-title-visible');
titleVisible = true;
}
}
});
/**
* The final thing is to add a tap listener that is called whenever the user taps on the screen.
* We do a quick check to make sure they're not tapping on the carousel indicators (tapping on
* those indicators moves you between items so we don't want to override that), then either hide
* or show the info Component.
*/
Ext.Viewport.element.on('tap', function(e) {
if (!e.getTarget('.x-carousel-indicator')) {
if (titleVisible) {
info.element.removeCls('apod-title-visible');
titleVisible = false;
} else {
info.element.addCls('apod-title-visible');
titleVisible = true;
}
}
});

The End of the Beginning

This was a really simple app that shows how easy it is to put these things together with Sencha Touch 2. Like with most stories though there's more to come so keep an eye out for parts 2 and 3 of this intergalactic adventure.

Continue reading

Like Android? Help us fix it

Near the end of last week's Sencha Touch 2 beta release blog post there was an appeal to the community to help raise awareness of a nasty flashing issue with Android 4.x phones. Every time you tried to use an animation on a web page the browser would flash, wait a bit, then finally perform the animation.

We filed a ticket on this about a week ago and thanks to your help (over 300 of you starred the issue), got a prompt response from the Android team with a fix for the flashing issue.

Getting it Right

However, that's only half the story. While the ugly flash is gone, animation performance on Android 4.x phones is still unacceptable. As it stands a 2 year old device running Android 2.x easily outruns the top of the range devices today running 4.x.

We really want to have excellent support for all Android devices. While 4.x accounts for only 1% of all Android phones today, that number is only going to go up. And when it does, we want to be ready to ship fast, fluid, beautiful apps onto it.

So we've created a new ticket with reduced, reproducible test cases and filed it to the bug tracker. We'll continue to give the Android team as much support as we can in order to resolve this quickly, but once again we'll need your help.

In fact all we need is a few seconds of your time. Just open the ticket and click the star at the top left. That's all we need - it tells the Android team just how many people care about this issue and will help them prioritize it accordingly.

If you want to help out more, take a moment to add a comment to the ticket outlining your own experiences with this issue, like the m.lanyrd.com developer did. Highlighting specific cases where you've had problems will really help.

Thanks!

Helping raise awareness of this issue will help everyone who uses or develops for Android devices on the web, and enables technologies like Sencha Touch to deliver slick, immersive apps without resorting to rewriting your app for each platform. We appreciate your help!

Star the issue now

Continue reading

Sencha Touch 2 Hits Beta

Earlier today we released Sencha Touch 2 Beta 1 - check out the official sencha.com blog post and release notes to find out all of the awesome stuff packed into this release.

This is a really important release for us - Sencha Touch 2 is another huge leap forward for the mobile web and hitting beta is a massive milestone for everyone involved with the project. From a personal standpoint, working on this release with the amazing Touch team has been immensely gratifying and I hope the end result more than meets your expectations of what the mobile web can do.

While you should check out the official blog post and release notes to find out the large scale changes, there are a number of things I'd really like to highlight today.

A Note on Builds

Before we get into the meat of B1 itself, first a quick note that we've updated the set of builds that we generate with the release. Previously there had been some confusion around which build you should be using in which circumstances so we've tried to simplify that.

Most people, most of the time should be using the new sencha-touch-debug.js while developing their app as it is unminified code that contains all of the debug warnings and comments. If you're migrating from 1.x, use the new builds/sencha-touch-all-compat.js build as it provides an easier migration path by logging additional warnings when you use 1.x-style class configurations.

Because we provide 5 builds in total we created a guide on the shipped builds and JSBuilder (the tool that creates a custom build specifically for your app). The guide contains a table showing all of the options enabled for each build - hopefully that makes it easy to choose which build is best for your needs.

Performance

In case you haven't seen Sencha Touch 2 yet the first thing you need to know is that it's fast. Crazy fast. Check out this side by side comparison between 1.x and 2.x:

Sencha Touch 2 Performance

Layout performance is enormously faster in 2.x due to a brand new layout engine that operates much closer to the browser's optimized CSS layout engine. The difference is pretty startling, especially on Android devices, which had sometimes struggled with Sencha Touch 1. Performance remains a top priority for us and we're really pleased with the improvements that we've secured with 2.0.

Navigation View

The new Navigation View is one of the slickest, sexiest things we've created for 2.0. I could play with this thing all day. If you've got a phone in your pocket or a tablet near by open up the Navigation View example and see it for yourself. If you're not, check out this beautiful video of it in action:

Getting Started with Sencha Touch 2

Navigation Views are really easy to put together and make your application immediately come to life. Check out the Navigation View docs to see how easy it is to add this to your own applications.

Awesome new examples

As of beta 1 we have 24 examples shipped with the SDK, including no fewer than 6 MVC examples - Kitchen Sink, Jogs with Friends, Twitter, Kiva, Navigation View and GeoCongress.

The Kitchen Sink and Twitter examples also take advantage of Device Profiles, which are a powerful way to customize your app to render customized UI for tablets and phones. Take a look at the Kitchen Sink on your phone and on an iPad to see how it rearranges itself depending on the screen size.

Finally, if you're seeing Sencha Touch 2 for the first time you may not have seen the new inline examples in the documentation center. This is a brand new thing for Sencha Touch and allows you to edit code live on the documentation page and immediately see the results - give it a go on the Carousel docs.

Ludicrous Amounts of Documentation

Speaking of docs, we have a stunning amount of learning material for Sencha Touch 2. We've been through all of the major classes, making sure that the functions are clearly documented and that each one has some great intro text that describes what the class does and how it fits in with the rest of the framework.

We've also created over 20 brand new guides for Sencha Touch 2, covering everything from getting started through to developing using MVC, using Components and creating custom builds for your applications. We've put a huge amount of effort into our docs for Sencha Touch 2 and I really hope it pays off for you guys and makes it easier than ever to create great mobile web apps.

Go Build Something

It's only beta 1 but we're very happy with the performance, stability, API and documentation of Sencha Touch 2. I think it's the best thing we've ever created, and really highlights what the mobile web is capable of. 2012 looks set to be a very exciting year for Sencha Touch so I hope you'll join us on the adventure and build something amazing with it.

Download Sencha Touch 2 Beta 1 Now

Continue reading

The Class System in Sencha Touch 2 - What you need to know

Sencha Touch 1 used the class system from Ext JS 3, which provides a simple but powerful inheritance system that makes it easier to write big complex things like applications and frameworks.

With Sencha Touch 2 we've taken Ext JS 4's much more advanced class system and used it to create a leaner, cleaner and more beautiful framework. This post takes you through what has changed and how to use it to improve your apps.

Syntax

The first thing you'll notice when comparing code from 1.x and 2.x is that the class syntax is different. Back in 1.x we would define a class like this:

MyApp.CustomPanel = Ext.extend(Ext.Panel, {
html: 'Some html'
});
MyApp.CustomPanel = Ext.extend(Ext.Panel, {
html: 'Some html'
});

This would create a subclass of Ext.Panel called MyApp.CustomPanel, setting the html configuration to 'Some html'. Any time we create a new instance of our subclass (by calling new MyApp.CustomPanel()), we'll now get a slightly customized Ext.Panel instance.

Now let's see how the same class is defined in Sencha Touch 2:

Ext.define('MyApp.CustomPanel', {
extend: 'Ext.Panel',

config: {
html: 'Some html'
}
});
Ext.define('MyApp.CustomPanel', {
extend: 'Ext.Panel',

config: {
html: 'Some html'
}
});

There are a few changes here, let's go through them one by one. Firstly and most obviously we've swapped out Ext.extend for Ext.define. Ext.define operates using strings - notice that both 'MyApp.CustomPanel' and 'Ext.Panel' are now wrapped in quotes. This enables one of the most powerful parts of the new class system - dynamic loading.

I actually talked about this in a post about Ext JS 4 last year so if you're not familiar you should check out the post, but in a nutshell Sencha Touch 2 will automatically ensure that the class you're extending (Ext.Panel) is loaded on the page, fetching it from your server if necessary. This makes development easier and enables you to create custom builds that only contain the class your app actually uses.

The second notable change is that we're using a 'config' block now. Configs are a special thing in Sencha Touch 2 - they are properties of a class that can be retrieved and updated at any time, and provide extremely useful hook functions that enable you to run any custom logic you like whenever one of them is changed.

Whenever you want to customize any of the configurations of a subclass in Sencha Touch 2, just place them in the config block and the framework takes care of the rest, as we'll see in a moment.

Consistency

The biggest improvement that comes from the config system is consistency. Let's take our MyApp.CustomPanel class above and create an instance of it:

var myPanel = Ext.create('MyApp.CustomPanel');
var myPanel = Ext.create('MyApp.CustomPanel');

Every configuration has an automatically generated getter and setter function, which we can use like this:

myPanel.setHtml('New HTML');
myPanel.getHtml(); //returns 'New HTML'
myPanel.setHtml('New HTML');
myPanel.getHtml(); //returns 'New HTML'

This might not seem much, but the convention applies to every single configuration in the entire framework. This eliminates the guesswork from the API - if you know the config name, you know how to get it and update it. Contrast this with Sencha Touch 1 where retrieving the html config meant finding some property on the instance, and updating it meant calling myPanel.update('New HTML'), which is nowhere near as predictable.

Instantiating

You probably noticed that we used a new function above - Ext.create. This is very similar to just calling 'new MyApp.CustomPanel()', with the exception that Ext.create uses the dynamic loading system to automatically load the class you are trying to instantiate if it is not already on the page. This can make life much easier when developing your app as you don't have to immediately manage dependencies - it just works.

In the example above we just instantiated a default MyApp.CustomPanel but of course we can customize it at instantiation time by passing configs into Ext.create:

var myPanel = Ext.create('MyApp.CustomPanel', {
html: 'Some Custom HTML'
});
var myPanel = Ext.create('MyApp.CustomPanel', {
html: 'Some Custom HTML'
});

We can still call getHtml() and setHtml() to retrieve and update our html config at any time.

Subclassing and Custom Configs

We created a simple subclass above that provided a new default value for Ext.Panel's html config. However, we can also add our own configs to our subclasses:

Ext.define('MyApp.CustomPanel', {
extend: 'Ext.Panel',

config: {
html: 'Some html',
anotherConfig: 'default value'
}
});
Ext.define('MyApp.CustomPanel', {
extend: 'Ext.Panel',

config: {
html: 'Some html',
anotherConfig: 'default value'
}
});

The 'anotherConfig' configuration doesn't exist on Ext.Panel so it's defined for the first time on MyApp.CustomPanel. This automatically creates our getter and setter functions for us:

var myPanel = Ext.create('MyApp.CustomPanel');
myPanel.setAnotherConfig('Something else');
myPanel.getAnotherConfig(); //now returns 'Something else'
var myPanel = Ext.create('MyApp.CustomPanel');
myPanel.setAnotherConfig('Something else');
myPanel.getAnotherConfig(); //now returns 'Something else'

Notice how the getter and setter names were automatically capitalized to use camelCase like all of the other functions in the framework. This was done automatically, but Sencha Touch 2 does another couple of very nice things for you - it creates hook functions:

Ext.define('MyApp.CustomPanel', {
extend: 'Ext.Panel',

config: {
html: 'Some html',
anotherConfig: 'default value'
},

applyAnotherConfig: function(value) {
return "[TEST] " + value;
},

updateAnotherConfig: function(value, oldValue) {
this.setHtml("HTML is now " + value);
}
});
Ext.define('MyApp.CustomPanel', {
extend: 'Ext.Panel',

config: {
html: 'Some html',
anotherConfig: 'default value'
},

applyAnotherConfig: function(value) {
return "[TEST] " + value;
},

updateAnotherConfig: function(value, oldValue) {
this.setHtml("HTML is now " + value);
}
});

We've added two new functions to our class - applyAnotherConfig and updateAnotherConfig - these are both called when we call setAnotherConfig. The first one that is called is applyAnotherConfig. This is passed the value of the configuration ('default value' by default in this case) and is given the opportunity to modify it. In this case we're prepending "[TEST] " to whatever anotherConfig is set to:

var myPanel = Ext.create('MyApp.CustomPanel');
myPanel.setAnotherConfig('Something else');
myPanel.getAnotherConfig(); //now returns '[TEST] Something else'
var myPanel = Ext.create('MyApp.CustomPanel');
myPanel.setAnotherConfig('Something else');
myPanel.getAnotherConfig(); //now returns '[TEST] Something else'

The second function, updateAnotherConfig, is called after applyAnotherConfig has had a chance to modify the value and is usually used to effect some other change - whether it's updating the DOM, sending an AJAX request, or setting another config as we do here.

When we run the code above, as well as '[TEST] ' being prepended to our anotherConfig configuration, we're calling this.setHtml to update the html configuration too. There's no limit to what you can do inside these hook functions, just remember the rule - the apply functions are used to transform new values before they are saved, the update functions are used to perform the actual side-effects of changing the value (e.g. updating the DOM or configuring other classes).

How we use it

The example above is a little contrived to show the point - let's look at a real example from Sencha Touch 2's Ext.Panel class:

applyBodyPadding: function(bodyPadding) {
if (bodyPadding === true) {
bodyPadding = 5;
}

bodyPadding = Ext.dom.Element.unitizeBox(bodyPadding);

return bodyPadding;
},

updateBodyPadding: function(newBodyPadding) {
this.element.setStyle('padding', newBodyPadding);
}
applyBodyPadding: function(bodyPadding) {
if (bodyPadding === true) {
bodyPadding = 5;
}

bodyPadding = Ext.dom.Element.unitizeBox(bodyPadding);

return bodyPadding;
},

updateBodyPadding: function(newBodyPadding) {
this.element.setStyle('padding', newBodyPadding);
}

Here we see the apply and update functions for the bodyPadding config. Notice that in the applyBodyPadding function we set a default and use the framework's unitizeBox function to parse CSS padding strings (like '5px 5px 10px 15px') into top, left, bottom and right paddings, which we then return as the transformed value.

The updateBodyPadding then takes this modified value and performs the actual updates - in this case setting the padding style on the Panel's element based on the new configuration. You can see similar usage in almost any component class in the framework.

Find out more

This is just a look through the most important aspects of the new class system and how they impact you when writing apps in Sencha Touch 2. To find out more about the class system we recommend taking a look at the Class System guide and if you have any questions the forums are a great place to start.

Continue reading

Sencha Touch 2 - Thoughts from the Trenches

As you may have seen, we put out the first public preview release of Sencha Touch 2 today. It only went live a few hours ago but the feedback has been inspiring so far. For the full scoop see the post on the sencha.com blog. A few thoughts on where we are with the product:

Performance

Performance on Android devices in particular is breathtaking. I never thought I'd see the day where I could pick up an Android 2.3 device and have it feel faster than an iPhone 4, and yet that's exactly what Sencha Touch 2 brings to the table. I recorded this short video on an actual device to show real world performance:

Video

Now try the same on Sencha Touch 1.x (or any other competing framework) and (if you're anything like me) cringe at what we were accustomed to using before. That video's cool, but the one that's really driving people wild is the side by side comparison of the layout engines in 1.x and 2.x.

Getting our hands on a high speed camera and recording these devices at 120fps was a lot of fun. Slowing time down to 1/4 of normal speed shows just how much faster the new layout engine is than what we used to have:

Video

The most amazing part here is that we actually finish laying out before the phone's rotation animation has completed. Skipping through the video frame by frame there are at least 5 frames where the app is fully laid out and interactive while the phone's rotation animation is still running. Beating the phone's own rotation speed is the holy grail - it's not possible to make it any faster.

Documentation

I'll admit it, I'm fanatical about great documentation. I'm sure I drive everyone else on the team crazy but I think it's worth it. This is only a preview release but it already contains by far the best, most complete documentation we've ever shipped in an initial release.

In fact, the team's worked so hard on documenting classes that it's probably better than the (already good) Ext JS 4 docs. Naturally, this makes it time to further improve the Ext JS documentation.

We've added some awesome features here - lots of videos, 11 brand new guides and illustrations. My favourite new feature is definitely the inline examples with live previews though - seeing Sencha Touch running live in a phone/tablet right there in the docs is just amazing. Little gems like the live twitter feed in the bottom-most example in the DataView docs really sell just how easy it is to configure these components.

We set a high bar for this though. We've gone from woeful documentation in 1.x to good documentation in 2.x, but what we're shooting for is excellence. We'll continue to round out our content over coming weeks, and have a few new features rolling out soon that will raise the bar once again.

Onwards

We have a few features left to implement, which is why we're calling this preview and not beta. Probably the biggest thing now is getting routing/deep linking back into the framework, along with a nice new syntax that I think you'll find really easy to use. We're also missing carousel animations and a handful of other things that will be going back in over the coming weeks. We have Sencha Con 2011 in just 12 days now though so we'll share more details there.

Finally though, I want to thank everyone who participated in the closed preview phase, and for everyone sending their support and kind words on the blog, the forums and on twitter. We really appreciate all the great feedback and I hope we can exceed your expectations with a fast, polished, gorgeous 2.0 final!

Continue reading

Proxies in Ext JS 4

One of the classes that has a lot more prominence in Ext JS 4 is the data Proxy. Proxies are responsible for all of the loading and saving of data in an Ext JS 4 or Sencha Touch application. Whenever you're creating, updating, deleting or loading any type of data in your app, you're almost certainly doing it via an Ext.data.Proxy.

If you've seen January's Sencha newsletter you may have read an article called Anatomy of a Model, which introduces the most commonly-used Proxies. All a Proxy really needs is four functions - create, read, update and destroy. For an AjaxProxy, each of these will result in an Ajax request being made. For a LocalStorageProxy, the functions will create, read, update or delete records from HTML5 localStorage.

Because Proxies all implement the same interface they're completely interchangeable, so you can swap out your data source - at design time or run time - without changing any other code. Although the local Proxies like LocalStorageProxy and MemoryProxy are self-contained, the remote Proxies like AjaxProxy and ScriptTagProxy make use of Readers and Writers to encode and decode their data when communicating with the server.

Ext.data.Proxy Reader and Writer

Whether we are reading data from a server or preparing data to be sent back, usually we format it as either JSON or XML. Both of our frameworks come with JSON and XML Readers and Writers which handle all of this for you with a very simple API.

Using a Proxy with a Model

Proxies are usually used along with either a Model or a Store. The simplest setup is just with a model:

var User = Ext.regModel('User', {
fields: ['id', 'name', 'email'],

proxy: {
type: 'rest',
url : '/users',
reader: {
type: 'json',
root: 'users'
}
}
});
var User = Ext.regModel('User', {
fields: ['id', 'name', 'email'],

proxy: {
type: 'rest',
url : '/users',
reader: {
type: 'json',
root: 'users'
}
}
});

Here we've created a User model with a RestProxy. RestProxy is a special form of AjaxProxy that can automatically figure out Restful urls for our models. The Proxy that we set up features a JsonReader to decode any server responses - check out the recent data package post on the Sencha blog to see Readers in action.

When we use the following functions on the new User model, the Proxy is called behind the scenes:

var user = new User({name: 'Ed Spencer'});

//CREATE: calls the RestProxy's create function because the user has never been saved
user.save();

//UPDATE: calls the RestProxy's update function because it has been saved before
user.set('email', 'ed@sencha.com');

//DESTROY: calls the RestProxy's destroy function
user.destroy();

//READ: calls the RestProxy's read function
User.load(123, {
success: function(user) {
console.log(user);
}
});
var user = new User({name: 'Ed Spencer'});

//CREATE: calls the RestProxy's create function because the user has never been saved
user.save();

//UPDATE: calls the RestProxy's update function because it has been saved before
user.set('email', 'ed@sencha.com');

//DESTROY: calls the RestProxy's destroy function
user.destroy();

//READ: calls the RestProxy's read function
User.load(123, {
success: function(user) {
console.log(user);
}
});

We were able to perform all four CRUD operations just by specifying a Proxy for our Model. Notice that the first 3 calls are instance methods whereas the fourth (User.load) is static on the User model. Note also that you can create a Model without a Proxy, you just won't be able to persist it.

Usage with Stores

In Ext JS 3.x, most of the data manipulation was done via Stores. A chief purpose of a Store is to be a local subset of some data plus delta. For example, you might have 1000 products in your database and have 25 of them loaded into a Store on the client side (the local subset). While operating on that subset, your user may have added, updated or deleted some of the Products. Until these changes are synchronized with the server they are known as a delta.

In order to read data from and sync to the server, Stores also need to be able to call those CRUD operations. We can give a Store a Proxy in the same way:

var store = new Ext.data.Store({
model: 'User',
proxy: {
type: 'rest',
url : '/users',
reader: {
type: 'json',
root: 'users'
}
}
});
var store = new Ext.data.Store({
model: 'User',
proxy: {
type: 'rest',
url : '/users',
reader: {
type: 'json',
root: 'users'
}
}
});

We created the exact same Proxy for the Store because that's how our server side is set up to deliver data. Because we'll usually want to use the same Proxy mechanism for all User manipulations, it's usually best to just define the Proxy once on the Model and then simply tell the Store which Model to use. This automatically picks up the User model's Proxy:

//no need to define proxy - this will reuse the User's Proxy
var store = new Ext.data.Store({
model: 'User'
});
//no need to define proxy - this will reuse the User's Proxy
var store = new Ext.data.Store({
model: 'User'
});

Store invokes the CRUD operations via its load and sync functions. Calling load uses the Proxy's read operation, which sync utilizes one or more of create, update and destroy depending on the current Store delta.

//CREATE: calls the RestProxy's create function to create the Tommy record on the server
store.add({name: 'Tommy Maintz'});
store.sync();

//UPDATE: calls the RestProxy's update function to update the Tommy record on the server
store.getAt(1).set('email', 'tommy@sencha.com');
store.sync();

//DESTROY: calls the RestProxy's destroy function
store.remove(store.getAt(1));
store.sync();

//READ: calls the RestProxy's read function
store.load();
//CREATE: calls the RestProxy's create function to create the Tommy record on the server
store.add({name: 'Tommy Maintz'});
store.sync();

//UPDATE: calls the RestProxy's update function to update the Tommy record on the server
store.getAt(1).set('email', 'tommy@sencha.com');
store.sync();

//DESTROY: calls the RestProxy's destroy function
store.remove(store.getAt(1));
store.sync();

//READ: calls the RestProxy's read function
store.load();

Store has used the exact same CRUD operations on the shared Proxy. In all of the examples above we have used the exact same RestProxy instance from three different places: statically on our Model (User.load), as a Model instance method (user.save, user.destroy) and via a Store instance (store.load, store.sync):

Data Proxy Reuse

Of course, most Proxies have their own private methods to do the actual work, but all a Proxy needs to do is implement those four functions to be usable with Ext JS 4 and Sencha Touch. This means it's easy to create new Proxies, as James Pearce did in a recent Sencha Touch example where he needed to read address book data from a mobile phone. Everything he does to set up his Proxy in the article (about 1/3rd of the way down) works the same way for Ext JS 4 too.

Continue reading

Introduction to Ext JS 4

At the end of last 2010 we capped off an incredible year with SenchaCon - by far the biggest gathering of Sencha developers ever assembled. We descended on San Francisco, 500 strong, and spent an amazing few days sharing the awesome new stuff we're working on, learning from each other, and addressing the web's most pressing problems.

Now, we're proud to release all of the videos from the conference completely free for everyone. You can see a full list on our conference site, where you'll find days worth of material all about Ext JS 4, Sencha Touch and all of the other treats we're working on at the moment.

Some of the videos in particular stand out for me - Jamie's Charting and Layouts talks were spectacular, as was Rob's Theming Ext JS 4 talk. On the Touch side, Tommy's talks on Performance and Debugging are required viewing, as is Dave Kaneda's characteristically off the cuff Theming talk.

My personal high point was standing in front of all of you and introducing Ext JS 4 and its three core goals - speed, stability and ease of use. I think you're going to love what we've done with the framework in version 4.

If you're so inclined, you can find the slides for this talk on slideshare, and if you can still stand the sound of my voice check out my other presentation on Ext JS 4 Architecture, focusing chiefly on the new data package (slides).

Continue reading

Ext JS 4: The Class Definition Pipeline

Last time, we looked at some of the features of the new class system in Ext JS 4, and explored some of the code that makes it work. Today we're going to dig a little deeper and look at the class definition pipeline - the framework responsible for creating every class in Ext JS 4.

As I mentioned last time, every class in Ext JS 4 is an instance of Ext.Class. When an Ext.Class is constructed, it hands itself off to a pipeline populated by small, focused processors, each of which handles one part of the class definition process. We ship a number of these processors out of the box - there are processors for handling mixins, setting up configuration functions and handling class extension.

The pipeline is probably best explained with a picture. Think of your class starting its definition journey at the bottom left, working its way up the preprocessors on the left hand side and then down the postprocessors on the right, until finally it reaches the end, where it signals its readiness to a callback function:

The distinction between preprocessors and postprocessors is that a class is considered ‘ready’ (e.g. can be instantiated) after the preprocessors have all been executed. Postprocessors typically perform functions like aliasing the class name to an xtype or back to a legacy class name - things that don't affect the class' behavior.

Each processor runs asynchronously, calling back to the Ext.Class constructor when it is ready - this is what enables us to extend classes that don’t exist on the page yet. The first preprocessor is the Loader, which checks to see if all of the new Class’ dependencies are available. If they are not, the Loader can dynamically load those dependencies before calling back to Ext.Class and allowing the next preprocessor to run. We'll take another look at the Loader in another post.

After running the Loader, the new Class is set up to inherit from the declared superclass by the Extend preprocessor. The Mixins preprocessor takes care of copying all of the functions from each of our mixins, and the Config preprocessor handles the creation of the 4 config functions we saw last time (e.g. getTitle, setTitle, resetTitle, applyTitle - check out yesterday's post to see how the Configs processor helps out).

Finally, the Statics preprocessor looks for any static functions that we set up on our new class and makes them available statically on the class. The processors that are run are completely customizable, and it’s easy to add custom processors at any point. Let's take a look at that Statics preprocessor as an example:

//Each processor is passed three arguments - the class under construction,
//the configuration for that class and a callback function to call when the processor has finished
Ext.Class.registerPreprocessor('statics', function(cls, data, callback) {
if (Ext.isObject(data.statics)) {
var statics = data.statics,
name;

//here we just copy each static function onto the new Class
for (name in statics) {
if (statics.hasOwnProperty(name)) {
cls[name] = statics[name];
}
}
}

delete data.statics;

//Once the processor's work is done, we just call the callback function to kick off the next processor
if (callback) {
callback.call(this, cls, data);
}
});

//Changing the order that the preprocessors are called in is easy too - this is the default
Ext.Class.setDefaultPreprocessors(['extend', 'mixins', 'config', 'statics']);
//Each processor is passed three arguments - the class under construction,
//the configuration for that class and a callback function to call when the processor has finished
Ext.Class.registerPreprocessor('statics', function(cls, data, callback) {
if (Ext.isObject(data.statics)) {
var statics = data.statics,
name;

//here we just copy each static function onto the new Class
for (name in statics) {
if (statics.hasOwnProperty(name)) {
cls[name] = statics[name];
}
}
}

delete data.statics;

//Once the processor's work is done, we just call the callback function to kick off the next processor
if (callback) {
callback.call(this, cls, data);
}
});

//Changing the order that the preprocessors are called in is easy too - this is the default
Ext.Class.setDefaultPreprocessors(['extend', 'mixins', 'config', 'statics']);

What happens above is pretty straightforward. We're registering a preprocessor called 'statics' with Ext.Class. The function we provide is called whenever the 'statics' preprocessor is invoked, and is passed the new Ext.Class instance, the configuration for that class, and a callback to call when the preprocessor has finished its work.

The actual work that this preprocessor does is trivial - it just looks to see if we declared a 'statics' property in our class configuration and if so copies it onto the new class. For example, let's say we want to create a static getNextId function on a class:

Ext.define('MyClass', {
statics: {
idSeed: 1000,
getNextId: function() {
return this.idSeed++;
}
}
});
Ext.define('MyClass', {
statics: {
idSeed: 1000,
getNextId: function() {
return this.idSeed++;
}
}
});

Because of the Statics preprocessor, we can now call the function statically on the Class (e.g. without creating an instance of MyClass):

MyClass.getNextId(); //1000
MyClass.getNextId(); //1001
MyClass.getNextId(); //1002
... etc
MyClass.getNextId(); //1000
MyClass.getNextId(); //1001
MyClass.getNextId(); //1002
... etc

Finally, let's come back to that callback at the bottom of the picture above. If we supply one, a callback function is run after all of the processors have run. At this point the new class is completely ready for use in your application. Here we create an instance of MyClass using the callback function, guaranteeing that the dependency on Ext.Window has been honored:

Ext.define('MyClass', {
extend: 'Ext.Window'
}, function() {
//this callback is called when MyClass is ready for use
var cls = new MyClass();
cls.setTitle('Everything is ready');
cls.show();
});
Ext.define('MyClass', {
extend: 'Ext.Window'
}, function() {
//this callback is called when MyClass is ready for use
var cls = new MyClass();
cls.setTitle('Everything is ready');
cls.show();
});

That's it for today. Next time we'll look at some of the new features in the part of Ext JS 4 that is closest to my heart - the data package.

Continue reading

Classes in Ext JS 4: Under the hood

Last week we unveiled a the brand new class system coming in Ext JS 4. If you haven’t seen the new system in action I hope you’ll take a look at the blog post on sencha.com and check out the live demo. Today we’re going to dig a little deeper into the class system to see how it actually works.

To briefly recap, the new class system enables us to define classes like this:

Ext.define('Ext.Window', {
extend: 'Ext.Panel',
requires: 'Ext.Tool',
mixins: {
draggable: 'Ext.util.Draggable'
},

config: {
title: "Window Title"
}
});
Ext.define('Ext.Window', {
extend: 'Ext.Panel',
requires: 'Ext.Tool',
mixins: {
draggable: 'Ext.util.Draggable'
},

config: {
title: "Window Title"
}
});

Here we’ve set up a slightly simplified version of the Ext.Window class. We’ve set Window up to be a subclass of Panel, declared that it requires the Ext.Tool class and that it mixes in functionality from the Ext.util.Draggable class.

There are a few new things here so we’ll attack them one at a time. The ‘extend’ declaration does what you’d expect - we’re just saying that Window should be a subclass of Panel. The ‘requires’ declaration means that the named classes (just Ext.Tool in this case) have to be present before the Window class can be considered ‘ready’ for use (more on class readiness in a moment).

The ‘mixins’ declaration is a brand new concept when it comes to Ext JS. A mixin is just a set of functions (and sometimes properties) that are merged into a class. For example, the Ext.util.Draggable mixin we defined above might contain a function called ‘startDragging’ - this gets copied into Ext.Window to enable us to use the function in a window instance:

//a simplified Draggable mixin
Ext.define('Ext.util.Draggable', {
startDragging: function() {
console.log('started dragging');
}
});
//a simplified Draggable mixin
Ext.define('Ext.util.Draggable', {
startDragging: function() {
console.log('started dragging');
}
});

When we create a new Ext.Window instance now, we can call the function that was mixed in from Ext.util.Draggable:

var win = Ext.create('Ext.Window');
win.startDragging(); //"started dragging"
var win = Ext.create('Ext.Window');
win.startDragging(); //"started dragging"

Mixins are really useful when a class needs to inherit multiple traits but can’t do so easily using a traditional single inheritance mechanism. For example, Ext.Windows is a draggable component, as are Sliders, Grid headers, and many other UI elements. Because this behavior crops up in many different places it’s not feasible to work the draggable behavior into a single superclass because not all of those UI elements actually share a common superclass. Creating a Draggable mixin solves this problem - now anything can be made draggable with a couple of lines of code.

The last new piece of functionality I’ll mention briefly is the ‘config’ declaration. Most of the classes in Ext JS take configuration parameters, many of which can be changed at runtime. In the Ext.Window above example we declared that the class has a ‘title’ configuration, which takes the default value of ‘Window Title’. By setting the class up like this we get 4 methods for free - getTitle, setTitle, resetTitle and applyTitle.

  • getTitle - returns the current title
  • setTitle - sets the title to a new value
  • resetTitle - reverts the title to its default value (‘Window Title’)
  • applyTitle - this is a template method that you can choose to define. It is called whenever setTitle is called.

The applyTitle function is the place to put any logic that needs to be called when the title is changed - for example we might want to update a DOM Element with the new title:

Ext.define(‘Ext.Window’, {
//..as above,

config: {
title: 'Window Title'
},

//updates the DOM element that contains the window title
applyTitle: function(newTitle) {
this.titleEl.update(newTitle);
}
});
Ext.define(‘Ext.Window’, {
//..as above,

config: {
title: 'Window Title'
},

//updates the DOM element that contains the window title
applyTitle: function(newTitle) {
this.titleEl.update(newTitle);
}
});

This saves us a lot of time and code while providing a consistent API for all configuration options: win-win.

Digging Deeper

Ext JS 4 introduces 4 new classes to make all this magic work:

  • Ext.Base - all classes inherit from Ext.Base. It provides basic low-level functionality used by all classes
  • Ext.Class - a factory for making new classes
  • Ext.ClassLoader - responsible for ensuring that classes are available, loading them if they aren’t on the page already
  • Ext.ClassManager - kicks off class creation and manages dependencies

These all work together behind the scenes and most of the time you won’t even need to be aware of what is being called when you define and use a class. The two functions that you’ll use most often - Ext.define and Ext.create - both call Ext.ClassManager under the hood, which in turn utilizes the other three classes to put everything together.

The distinction between Ext.Class and Ext.Base is important. Ext.Base is the top-level superclass for every class ever defined - every class inherits from Ext.Base at some point. Ext.Class represents the class itself - every class you define is an instance of Ext.Class, and a subclass of Ext.Base. To illustrate, let’s say we created a class called MyClass, which doesn’t extend any other class:

Ext.define('MyClass', {
someFunction: function() {
console.log('Ran some function');
}
});
Ext.define('MyClass', {
someFunction: function() {
console.log('Ran some function');
}
});

The direct superclass for MyClass is Ext.Base because we didn’t specify that MyClass should extend anything else. If you imagine a tree of all the classes we’ve defined so far, it will look something like this:

Sample Inheritance Tree

This tree bases its hierarchy on the inheritance structure of our classes, and the root is always Ext.Base - that is, every class eventually inherits from Ext.Base. So every item in the diagram above is a subclass of Ext.Base, but every item is also an instance of Ext.Class. Classes themselves are instances of Ext.Class, which means we can easily modify the Class at a later time - for example mixing in additional functionality:

//we can define some mixins at definition time
Ext.define('MyClass', {
mixins: {
observable: 'Ext.util.Observable'
}
});

//it’s easy to add more later too
MyClass.mixin('draggable', 'Ext.util.Draggable');
//we can define some mixins at definition time
Ext.define('MyClass', {
mixins: {
observable: 'Ext.util.Observable'
}
});

//it’s easy to add more later too
MyClass.mixin('draggable', 'Ext.util.Draggable');

This architecture opens up new possibilities for dynamic class creation and metaprogramming, which were difficult to pull off in earlier versions.

In the next episode, we’ll look at how the class definition pipeline is structured and how to extend it to add your own features.

Continue reading