вторник, 21 августа 2012 г.

Google Apps Script - язык для удобства )

В поисках материалов по интернету о Google App Engine попалась информация по интересному проекту Google Apps Script. Очень многообещающе - начал изучать. Очередной сюрприз от Google для меня :) Не устаю удивляться.
Надо будет попереводить, потренироваться. 

вторник, 29 мая 2012 г.

Google App Engine. Обзор хранилища данных


Среда App Engine предоставляет несколько вариантов для хранения данных:
  • App Engine Datastore предоставляет NoSQL безсхемное хранилище с языком запросов и атомарными транзакциями
  • Google Cloud SQL предоставляет реляционную SQL базу данных для приложений App Engine, основываясь на знакомой базе MySQL
  • Google Cloud Storage предоставляет сервис хранения для объектов и файлов размером до терабайтов
Python API хранилища данных
App Engine Datastore это безсхемное объектное хранилище предоставляющее надежное, масштабируемое хранилище для вашего вэб-приложения без плановых простоев, с атомарными транзакциями, высокодоступными чтением и записью, строгой согласованностью при запросах чтения и в конечном итоге всех иных запросов. Интерфейс хранилища включает в себя обширное API моделирования данных и похожий на SQL язык запросов, называемый GQL.
Вступление
Хранилище App Engine содержит объекты с данными, так называемыми сущностями. Сущность имеет одно или множество свойств — именованных значений одного из нескольких поддерживаемых типов данных: например свойство может быть строкой, числом или ссылкой на другую сущность. Каждая сущность идентифицируется по виду (типу), который определяет ее для выполнения запросов, а также уникальный ключ, который идентифицирует ее внутри этого вида.
Хранилище может выполнять множество операций в одной транзакции. По-определению транзакция не может быть успешно закончена, пока каждая из ее операций не выполнена успешно, если любая из операций прервана, следует автоматический откат транзакции. Это особенно полезно при распределенных приложениях где множество пользователей могут одновременно получать доступ или манипулировать одними данными в один момент времени.
Репозитарий данных
Основной репозитарий данных App Engine это High Replication Datastore (HRD), высокореплицируемое хранилище, в котором данные реплицируются во множестве центров данных с помощью системы, основанной на алгоритме Pasox. Это предоставляет высокий уровень доступности для чтения и записи. Большинство запросов в конечном счете согласованы.
Второй вариант хранилища — Master/Slave перестал поддерживаться в апреле 2012 года. Хотя Google продолжает поддержку Master/Slave хранилищ в соответствии с условиями обслуживания, рекомендуется чтобы все новые приложения использовали HRD, а уже существующие приложения использующие Master/Slave хранилища мигрировали на HRD. Только HRD поддерживается в Python 2.7.
Сравнение с традиционными базами данных
В отличие от традиционных реляционных БД, App Engine использует распределенную архитектуру для автоматического управления масштабированием до очень больших наборов данных. И несмотря на то, что интерфейс хранилища имеет множество похожих на традиционные БД возможностей он отличается от них тем, как описываются связи между объектами данных. Сущности одного типа могут иметь различные свойства, а различные сущности могут иметь свойства с теми же именами но другими типами значений.
Эти уникальные характеристики предполагают иные пути для моделирования и управления данными для получения возможности автоматического масштабирования. В частности App Engine отличается от традиционных реляционных БД по следующим важным характеристикам:
- App Engine спроектирована для масштабирования, позволяющего приложениям обеспечивать высокую производительность при большом трафике:
  • запись в хранилище масштабируется автоматически распределяется по мере необходимости
  • чтение из хранилища масштабируется так как единственные поддерживаемые запросы те, чья производительность масштабируется согласно результирующему выходному набору данных (в отличие от просто набора данных). Это означает что запрос, выдающий в ответе 100 сущностей выполняется также как если бы поиск происходил по сотням или миллионам записей. Это свойство - ключевая причина, по которой не поддерживаются некоторые типы запросов.
- так как все запросы в App Engine строятся на основе предварительно построенных индексов, типы запросов, которые могут выполняться более ограничены, чем позволенные в реляционных базах данных. В частности не поддерживаются следующие типы запросов:
  • Join-операции объединения
  • фильтрация по неравенству по нескольким свойствам
  • фильтрация или данные, основанные на результатах вложенных запросов
- В отличии от традиционных реляционных баз данных, хранилище App Engine может работать с сущностями одного типа с различными не строго определенными наборами наборами свойств (хотя вы можете сами наложить эти ограничения в приложении). В настоящий момент невозможно выполнить запрос, возвращающий только подмножество результирующих свойств сущностей.

Для получения более детальной информации по архитектуре хранилища читайте нашу серию статей Mastering the Datastore.

Введение в Python Datastore API
В Python сущности хранилища создаются из объектов Python; атрибуты объекта становятся свойствами сущности. Для создания новой сущности вы вызываете базовый класс родительской сущности (если таковая имеется), устанавливает атрибуты объектов, а затем сохраняет объект (вызывая такой метод как put()).
import datetime
from google.appengine.ext import db
from google.appengine.api import users


class Employee(db.Model):
 name = db.StringProperty(required=True)
 role = db.StringProperty(required=True, choices=set(["executive", "manager", "producer"]))
 hire_date = db.DateProperty()
 new_hire_training_completed = db.BooleanProperty(indexed=False)
 account = db.UserProperty()


e = Employee(name="John",
            role="manager",
            account=users.get_current_user())
e.hire_date = datetime.datetime.now().date()
e.put()

API хранилища предоставляет два интерфейса для запросов: интерфейс объектных запросов и похожий на SQL язык запросов, называемый GQL. Запрос возвращает сущности в форме экземпляра объекта класса модели, который можно изменить и обратно поместить в хранилище:

training_registration_list = [users.User("Alfred.Smith@example.com"),
                             users.User("jharrison@example.com"),
                             users.User("budnelson@example.com")]
employees_trained = db.GqlQuery("SELECT * FROM Employee WHERE account IN :1",
                               training_registration_list)
for e in employees_trained:
 e.new_hire_training_completed = True
 db.put(e)


Сущности
Объекты в хранилище App Engine известны как сущности. Сущность имеет одно или несколько именованных свойств, каждое из которых может иметь одно или несколько значений. Значения свойств могут быть данными различного типа, включая целые числа, числа с плавающей запятой, строки, даты, бинарные данные и многие другие. Запрос по свойству с множеством значений проверяет, соответствует ли какое-нибудь из значений нужному критерию запроса. Такие свойства очень удобны при проверке совокупности значений.
Примечание: сущности хранилища безсхемные, как в традиционных реляционных БД, хранилище App Engine не требует, чтобы все сущности одного типа имели одинаковый набор свойств или чтобы все значения свойств сущности были одинакового типа данных. Если потребуется формальная схема. то само приложение ответственно за то, чтобы сущности соответствовали ей; Python SDK включает богатую библиотеку возможностей моделирования данных для этого.

Типы, ключи и идентификаторы
Каждая сущность хранилища имеет свой тип или вид, который классифицирует сущность по назначению запроса: например, приложение управления человеческими ресурсами может представлять каждого работника в компании с помощью сущности типа Employee. Также, каждая сущность имеет собственный ключ, который однозначно ее идентифицирует. Ключ состоит из следующих компонентов:
  • Тип сущности
  • Опциональный родительский путь определяющий сущность в иерархии хранилища
  • Идентификатор, который может быть:
  • ключевой строкой
  • целым числом ID
Идентификатор ассоциируется с сущностью при ее создании. Так как он является частью ключа сущности, он ассоциируется с сущностью навсегда и не может изменяться. Он может быть присвоен двумя путями:
Ваше приложение может само указать ключевую строку-значение для сущности
Вы можете определить для хранилища автоматическое присвоение идентификатору численное значение ID
Примечание: вместо именованных строковых значений или генерирования числовых значений ID автоматически, сложные приложения могут иногда присваивать собственные числовые значения ID сущностям, которые они создают. Будьте внимательны, если вы выберите этот путь, вам придется предпринять специальные меры, чтобы предотвратить возможные конфликты вручную присвоенных ID с автоматически-сгенерированными хранилищем, смотрите Сущности, свойства и Ключи для подробной информации.

Родительские пути
Сущности в хранилище формируют иерархическую структуру, похожую на структуру директорий файловой системы. При создании сущности вы опционально можете указать другую сущность как родительскую; новая сущность будет потомком родительской сущности. Эта связь между сущностью и ее родителем неразрывна и ее нельзя будет изменить после ее создания. Сущность у которой нет родителя является корневой (root) сущностью. Хранилище никогда не присвоит двум сущностям с одинаковым родителем одинаковый ID, также как и двум корневым сущностям.
Родитель сущности, родитель родителя и так далее по рекурсии являются предками. Ее наследники, потомки потомков называются потомками. Сущность и ее потомки принято называть одной группой потомков. Последовательность сущностей, начиная с корневой и следую от родителя к потомкам до указанной сущности составляю родительский путь или путь предков сущности. Полный ключ, идентифицирующий сущность состоит из последовательности пар идентификатор-тип, указывающих их родительский путь и заканчивающихся на самой сущности:
Person:GreatGrandpa / Person:Grandpa / Person:Dad / Person:Me

У корневой сущности родительский путь пуст и ключ состоит только из собственно типа и идентификатора.
Person:GreatGrandpa


Запросы и индексы
Кроме прямого получения сущностей из хранилища по их ключам, приложение может выполнять запрос для их получения по значениям свойств. Запрос работает с сущностями указанного типа, он может фильтровать сущности по значениям свойств или ключей и может возвращать ноль и более сущностей в результате. (Для экономии памяти и увеличения производительности запросу следует где возможно указывать лимит на количество результирующих данных). Запросу также можно указать порядок сортировки по значениям свойств. Результат включает все сущности, которые имеют хотя бы одно (возможно нулевое) значение для каждого свойства, указанного в фильтре и порядке сортировки и свойства которых соответствуют указанному в фильтре критерию. Запрос может возвращать всю сущность, ее проекцию или только ключи.
Запрос может также включать родительский фильтр, ограничивающий результаты только группой потомков указанного родителя. Такие запросы известны также как родительские запросы. По-умолчанию родительские запросы возвращают строго соответствующие результаты, которые гарантировано синхронизированы с последними изменениями в данных. Неродительские запросы напротив, могут охватывать все хранилище, а не только одну группу сущностей, которые в конечном счете соответствуют реальным, но могут возвращать устаревшие данные. Если приложению важно строгое соответствие, то вам необходимо учесть это при структурировании данных, размещая связанные сущности в той же группе сущностей, так что их можно будет получить с помощью родительского а не обычного запроса; смотрите для подробностей Структурирование данных для точной согласованности.
Примечание: соблюдение согласованности немного отличается в Master/Slave хранилищах; смотрите Использование Master/Slave хранилищ для детального ознакомления.
Помимо фильтров  в запросе можно указать порядок сортировки по значениям свойств. Результаты запроса будут включать все сущности, которые имеют по крайней мере одно (возможно нулевое) значение для каждого свойства, указанного в фильтре и порядке сортировки и чьи свойства соответствуют указанным критериям фильтра. Запрос может возвращать целиком сущности, указанное подмножество их свойств или просто их ключи (в ключевых запросах).
Примечание: для экономии памяти и увеличения производительности запросы следует где только возможно ограничивать по количеству выдаваемых результатов.
Каждый запрос использует индекс - таблицу, содержащую потенциальные результаты запроса в необходимом порядке. Хранилище обновляет индексы постепенно для отражения любых изменений, которые приложение делает с сущностями. Таким образом, корректные результаты всех запросов мгновенно доступны напрямую из индексов, без необходимости проведения дополнительных расчетов.
Индексы для некоторых типов запросов предоставляются автоматически; приложение может определить дополнительные индексы для себя в файле индексной конфигурации под названием index.yaml. Вэб сервер разработчика автоматически добавляет предложения в конфигурационный файл, когда находит запросы, у которых еще не сконфигурированы индексы. Вы можете вручную настроить индексы, отредактировав файл перед загрузкой приложения, смотрите статью Выбор индексов и расширенный поиск для дополнительной информации.
Примечание: Этот индексный механизм поддерживает широкий спектр запросов и подходит для большинства приложений. Однако он не поддерживает некоторые типы запросов, общие для других технологий БД, в частности объединение и запросы агрегирования не поддерживаются в это механизме запросов.

Транзакции
Каждая попытка создать, обновить или удалить сущность происходит в контексте транзакции. Отдельная транзакция может содержать любое количество таких операций. Для поддержки целостности данных, транзакция проходит если все ее операции применены к хранилищу успешно как блок или если хоть одна не завершилась успешно, отменяется весь  блок операций.
Вы можете выполнить множество действий с сущностью внутри одной транзакции. Например для увеличения поля счетчика объекта вам необходимо считать значение счетчика, рассчитать новое значение и сохранить его обратно. Без использования транзакции возможна ситуация, когда другой процесс изменит значение поля счетчика в промежуток времени между тем, когда вы его прочитали и моментом обновления счетчика и ваше приложение перезапишет это значение своим.Использование транзакций обеспечит уверенность в том, что никакой другой процесс в это время не повлияет на данные.

Транзакции и группы сущностей
Внутри транзакции разрешены только родительские запросы, а это значит что каждый запрос ограничен одной отдельной группой сущностей. Однако сама по себе транзакция может применяться к множеству сущностей, которые могут как принадлежать к отдельной группе (в случае кросс-групповых транзакций) так и включать до пяти различных групп сущностей.
Хранилище использует оптимистичный параллелизм для управления транзакциями. Когда два и более экземпляров приложения пытаются изменить одну группу сущностей в один момент времени (как обновляя сущности так и создавая новые), первое приложение пытающееся внести изменение сможет это сделать, а другие нет. Эти другие приложения могут позже попытаться еще раз внести изменения в уже обновленные первым приложением данные. Помните, что это ограничивает количество параллельных записей, которые вы можете сделать в любую сущность в данной группе сущностей.

Кросс-групповые транзакции
Транзакция с сущностями, принадлежащими к разным группам, называется кросс-групповая (XG) транзакция. В пределах одной транзакции можно работать максимум с пятью группами сущностей и будет завершена успешно, если ни одна из групп не будет затронута другими параллельными транзакциями.
В транзакции с одиночной группой вы не можете выполнять неродительские запросы в XG транзакции. Однако вы можете выполнять родительские запросы на отдельных группах сущностей. Нетранзакционные (неродительские) запросы могут видеть все, некоторые или никакие результаты предыдущих выполненных транзакций (подробнее об этом в Запись в хранилище и видимость данных). Однако такие нетранзакционные запросы лучше всего подходят, чтобы увидеть результаты частично выполненной XG транзакции, чем такие же частично выполненные одиночные групповые транзакции.

Примечание: первая попытка чтения группы сущностей при XG запросах может вызвать исключение TransactionFailedError, если есть конфликт с другой транзакцией, работающей с этой группой сущностей в этот момент. Это означает, что даже XG транзакция, пытающаяся просто считать данные потерпит неудачу и вызовет исключение согласованности данных.
XG транзакция, которая затрагивает только одну группу сущностей ведет себя также как транзакция с одиночной группой. Операции в такой транзакции имеют такую же производительность и стоимость как и эквивалентная одиночно-групповая транзакция с экономией ресурсов и биллинга, но с более высокой задержкой.

Запись в хранилище и видимость данных.
Примечание: эта секция описывает работу с High replication datastore (HRD). Устаревшие Master/Slave хранилища ведут себя по-другому; смотрите Использование Master/Slave хранилища. Для дополнения к этому топику смотрите также статьи Жизнь записи в хранилище и Изоляция транзакций в App Engine.

Данные записываются в хранилище в две фазы:
1. В фазе фиксации (Commit) данные сущности записываются в журнал
2. Фаза применения состоит из двух параллельных действий:
  • Данные сущности записываются
  • Записывается индексный ряд для сущности (этот процесс может занять больше времени чем простая запись данных)
Операция записи возвращается сразу после фазы Commit а фаза применения происходит асинхронно. Если произошла ошибка при первом шаге Commit, то он автоматически повторяется, однако если ошибки продолжаются, хранилище возвращает сообщение об ошибке, которое ваше приложение принимает как исключение. Если Commit фаза проходит, а фаза применения вызывает ошибку то она переносится вперед до завершения пока не произойдет следующее:
Периодическая зачистка хранилища на предмет незавершенных Commit операций и их применение
Некоторые операции применения (get, put, delete и родительские запросы) которые используют затронутые группы сущностей не будут выполнятся пока не будут завершены предыдущие предыдущие.

четверг, 24 мая 2012 г.

Работа с изображениями на canvas с помощью JavaScript

Начинаем

Настало время что-нибудь нарисовать на нашем экране. Сначала нам понадобится HTML страница на которой собственно и будет отображаться canvas элемент.




     <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
     <html lang="en">
        <head>
           <title>JavaScript Platformer 1</title>
           <script type="text/javascript" src="jsplatformer1.js"></script>
           <style type="text/css">
              body { font-family: Arial,Helvetica,sans-serif;}
           </style>
        </head>
       <body>
          <p>
             <a href="http://www.brighthub.com/internet/web-development/articles/38364.aspx">
                Game Development with Javascript and the canvas element
             </a>
          </p>
          <canvas id="canvas" width="600" height="400">
             <p>Your browser does not support the canvas element.</p>
          </canvas>
       </body>
    </html>


В коде страницы все достаточно просто и понятно. Есть два важных элемента:
<script type="text/javascript" src="jsplatformer1.js"></script>
Эта строчка подключает файл с кодом JavaScript, с помощью которого будет изменяться canvas элемент, который будет описан ниже.
<canvas id="canvas" width="600" height="400"> <p>Your browser does not support the canvas element.</p> </canvas>
Так мы создаем элемент canvas. Броузеры, которые не поддерживают элемент canvas, такие как IE (до версии 8), игнорируют его, отображая вместо него дочерний элемент. В нашем случае это простой параграф, предупреждающий пользователя. что их браузер не поддерживает элемент canvas. Все броузеры, которые этот элемент поддерживают - не отображают его дочерний элемент.
Атрибут ID элемента canvas очень важен, так как понадобится нам для получения ссылки на этот элемент с помощью JavaScript. Атрибуты ширины и высоты просто указывают размеры canvas, как и для других html элементов вроде таблицы или изображения.
Содержимое файла jsplatformer1.js:


   


     // target frames per second
     const FPS = 30;
     var x = 0;
     var y = 0;
     var xDirection = 1;
     var yDirection = 1;
     var image = new Image();
     image.src = "http://javascript-tutorials.googlecode.com/files/jsplatformer1-smiley.jpg";
     var canvas = null;
    var context2D = null;

    window.onload = init;

    function init()
    {
       canvas = document.getElementById('canvas');
       context2D = canvas.getContext('2d');
       setInterval(draw, 1000 / FPS);
    }

    function draw()
    {
       context2D.clearRect(0, 0, canvas.width, canvas.height);
       context2D.drawImage(image, x, y);
       x += 1 * xDirection;
       y += 1 * yDirection;

       if (x >= 450)
       {
          x = 450;
          xDirection = -1;
       }
       else if (x <= 0)
       {
          x = 0;
          xDirection = 1;
       }

       if (y >= 250)
       {
          y = 250;
          yDirection = -1;
      }
       else if (y <= 0)
       {
          y = 0;
          yDirection = 1;
       }
    }


Сам по себе элемент canvas достаточно прост и бесполезен. Необходимо с помощью JavaScript что-нибудь отрисовывать на canvas и в нашем случае этот код находится в файле jsplatformer1.js. В качестве простого примера мы загружаем изображение, отрисовываем его на canvas и двигаем его.
Сначала определим несколько глобальных переменных:
const FPS = 30;
FPS определяет частоту с которой будет перерисовываться canvas.
var x = 0; var y = 0; var xDirection = 1; var yDirection = 1;
Переменные x, y, xDirection и yDirection используются для определения положения изображения (относительно левого верхнего угла canvas) и указывают направление движения изображения.
var image = new Image(); image.src = "http://javascript-tutorials.googlecode.com/files/jsplatformer1-smiley.jpg";

Чтобы отрисовать изображение на canvas нам сначала необходимо загрузить это изображение. Для этого мы создаем объект Image и устанавливаем свойство src на месторасположение этого файла.

var canvas = null;
var context2D=null;

Нам также понадобится ссылка на элемент canvas, также как и на отрисовывамый контекст. Мы присвоим необходимые значения элементу позже, а пока оставим их в null.

windows.onload = init;

В конце нам необходимо дать знать, когда страница будет загружена, чтобы запустить код который отрисует canvas, поэтому мы указываем функцию init, которая будет вызвана при отработке события окна после загрузки.




html 5. Об элементе Canvas


Элемент canvas появившийся в JavaScript движках наряду с некоторыми другими возможностями дает вэб-разработчикам возможность создавать детализированные и интерактивные 2D вэбстраницы без использования сторонних 3D плагинов. В этом топике обсудим некоторые возможности canvas и его потенциальные возможности.

JavaScript и элемент Canvas
HTML начал жизнь как формат для статичных страниц. Анимированные GIF изображения и мигающий текст - вот и вся динамика, которую он мог предоставить. JavaScript изменил все, позволяя динамическое изменение вэб-страниц и многие вэб-сервисы теперь используют и технологию AJAX для создания сайтов, которые дают пользователю более привычную работу, чем "нажал, обновилась страница, нажал", которая обычно так часто используется в обычных html-страницах.
К сожалению, JavaScript несколько ограничен функциональностью браузеров, в которых он работает. И если вы вполне можете создавать и изменять элемент на странице, то с помощью JavaScript вы не сможете (просто) заставить клиентскую машину отображать новый тип объектов. JavaScript может изменять текст, вставлять изображение или с легкостью изменять таблицу, так как эти элементы уже поддерживаются в HTML. Но что, если вы захотите чего-то больше, как например создать игру? Все, что вы могли использовать это стандартные элементы html таким образом, для которого они вовсе не предназначены. Ну или вам пришлось бы использовать плагины вроде Flash или Silverlight.

Появился элемент canvas. Этот новый HTML элемент предоставляет возможность JavaScript разработчикам рисовать прямо на странице без использования плагинов. Он впервые был представлен компанией Apple в их фреймворке WebKit и используется браузером Safari и виджетами Dashboard. Элемент canvas теперь является частью html 5 и теперь поддерживается большинством браузеров, таких как Chrome, Firefox, Opera и Konqueror. Internet Explorer (по-крайней мере до версии 8)  является исключением, хотя проекты вроде ExplorerCanvas предоставляют некоторую функциональность  элемента canvas в IE.
Любой кто работал с программированием 2D графики увидит что использование элемента canvas достаточно легкое и простое. Вы можете рисовать линии, фигуры, градиенты и даже изменять отдельные пиксели с помощью функций, которые очень похожи на большинство подобных в других 2D API. И огромное спасибо производительности последних JavaScript движков Chrome V8, Firefox SpiderMonkey и Safari Nitro за то что создание детализированных и интерактивных вэб-приложений теперь вполне доступно.
В этой серии статей мы посмотрим как создать простую игру с помощью JavaScript и элемента canvas. Мы раскроем такие темы как анимация, загрузка ресурсов, рендеринг слоев, прокрутка и взаимодействие.

пятница, 30 июля 2010 г.

ExtJS и jQuery

Ну вот наконец стал использовать в рабочих проектах ExtJS и jQuery
Ext конечно же использую для создания панелей управления сайтами - чудно и практично, а главное люди довольны что сами могут содержимым сайта управлять просто и понятно как в обычных программах, а не в лохматых непонятных самоделках или стандартных монстерских панелях открытых проектов, где сам чорт ногу сломит, не то что новичок.
jQuery также приятно радует, отличная библиотечка для движка, динамика некоторых просто восхищяет, когда сайт "расплывается" и трансформируется или меняется расцветка прямо на их глазах :)
Вообщем связка PHP MySQL jQuery ExtJS для меня теперь становиться стандартом :) Ну очень я не люблю всякий ширпотреб проектов "на любой случай", хотя конечно не спорю - многим нравится. А я люблю проекты с "индивидуальным подходом".
Чего и всем желаю!

вторник, 20 января 2009 г.

jQuery 1.3

Вышел новый релиз jQuery - 1.3
Отмечается значительное увеличение производительности и скорости работы библиотеки.
Подробнее

суббота, 3 января 2009 г.

Приложения в ExtJS

До того как начать

Это пособие подразумевает, что вы уже установили ExtJS - JavaScript библиотеку и что она установлена в директории extjs в вышестоящей от текущей директории, где будет создаваться наше приложение. Если вы установили библиотеку в другом месте вам необходимо будет изменять пути которые мы употребляем в примерах.
Что нам понадобится?

Помимо ExtJS нам понадобится создать два файла:

* applayout.html
* applayout.js


Давайте начнем с файлов с минимумом содержимого. Вот они с детальным описанием:

applayout.html
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" 
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">

<link rel="stylesheet" type="text/css" href="../extjs/resources/css/ext-all.css">
<script type="text/javascript" src="../extjs/adapter/ext/ext-base.js"></script>

<script type="text/javascript" src="../extjs/ext-all-debug.js"></script>
<script type="text/javascript" src="applayout.js"></script>

<!-- A Localization Script File comes here -->
<script type="text/javascript">
Ext.onReady(myNameSpace.app.init, myNameSpace.app);
</script>
<title>Application Layout Tutorial</title>

</head>
<body>
 
<div id="btn1-ct"></div>
 
</body>
</html>


Первые две строчки указывают тип документа. Наше приложение будет работать и без указания типа, однако в этом случае броузер перейдет в режим Quirks и приложение не станет совместимым с другими броузерами.
Мы будем использовать HTML 4.0.1 спецификацию, потому что она поддерживается большинством броузеров. Конечно, вы можете использовать другой тип документа если захотите, однако важно помнить, что вы всегда должны указывать тип документа.

ОБНОВЛЕНИЕ: Если вы будете указывать тип документа, то в броузерах IE (всех версиях) есть баги, которые могут вызвать непредсказуемые результаты выполнения. Поэтому больше не рекомундуется указывать тип документа в приложениях.

После начала файла и заголовка head идет указание типа содержимого Content-Type. Ваше приложение будет работать и без него, но лучше указать его.

Затем мы подключаем Ext стили, адаптер и ExtJS. Есть две версии ExtJS:
* ext-all.js - плохо-читаемый, загружается быстрее, используется для готовых приложений
* ext-all-debug.js - легко-читаемый, используется для разработки

Мы собираемся разрабатывать, поэтому включаем вторую версию.
Затем идет наше приложение applayout.js, а после него файлы локализации которые перекрывают английский текст в приложении applayout.js и ExtJS своим переводом, до того как приложение стартует.
Теперь мы готовы установить обработчик событий, который инициализирует (стартует) наше приложение после того, как документ полностью загрузится.
Строка

Ext.onReady(myNameSpace.app.init, myNameSpace.app);


говорит: Когда документ полностью загрузился, запустить init метод myNameSpace.app в области myNameSpace.app
Затем идет текст заголовка, конец head (пустой) и закрывающиеся тэги.
Это все, что сейчас находится в html файле.
applayout.js

/**
* Application Layout
* by Jozef Sakalos, aka Saki
* http://extjs.com/learn/Tutorial:Application_Layout_for_Beginners
*/

 
// reference local blank image
Ext.BLANK_IMAGE_URL = '../extjs/resources/images/default/s.gif';
 
// create namespace
Ext.namespace('myNameSpace');

 
// create application
myNameSpace.app = function() {
// do NOT access DOM from here; elements don't exist yet
 

// private variables
 
// private functions
 
// public space
return {
// public properties, e.g. strings to translate

 
// public methods
init: function() {
alert('Application successfully initialized');
}

};
}(); // end of app
 
// end of file


Первые строки этого файла это комментарии, объясняющие содержимое файла, автора и другую связанную информацию. Уверен, ваши приложения будут работать и без комментариев, но запомните: всегда пишите ваше приложение так, как будто пишите для другого человека. Когда вы когда-нибудь снова вернетесь к этому коду через пару месяцев и не сможете понять чтоже такое написано здесь, потому как нет комментариев, вы измените вашу практику кодирования.
Затем идет ссылка на пустое изображение, указывающее на ваш собственный сервер взамен сервера по-умолчанию extjs.com. Вам ведь не надо загружать что-либо с extjs.com при каждой загрузке приложения, не так ли?
Теперь мы создаем собственное пространство имен. Причина этого в инкапсуляции (изолированиии) всех переменных и методов в одном глобальном объекте чтоб избежать конфликтов имен переменных и избежать изменнений их значений извне.
Ядро приложения в конце.. Мы создаем свойство app в myNameSpace как возвращаемое значение функции которая запускается немедленно.
Если мы запустим следующий код
var o = function() {
return {p1:11, p2:22};

}();


Мы по-сути создаем анонимную функцию (функцию без имени) которая запускается сразу же после обработки (помните () полсе определения функции). Функция возвращает объект который привязывается к переменной о. Наше приложение будет использовать туже логику.
Вы можете разместить инициализацию, частные переменные и\или определения частных функций между началом объявление функции и моментом возврата результата, но помните: Код запускается как часть заголовка и никакие html элементы еще не созданы поэтому любые попытки доступа к ним вызовут ошибку.
С другой стороны, init функция это просто один из методов возвращаемых этой анонимной функцией, запускается только после того, как весь документ полностью загружен и вся структура DOM уже доступна.
Дальше - больше. Если вы не сделали никаких ошибок вы получите сообщение об успехе когда запустите ваше приложение.
Теперь идет самая сложная часть. Это написать что-то работающее внутри этого пустого шаблона.

Public, Private, Privileged?

Давайте добавим некоторую жизнь нашему приложению. Добавьте следующие строки в applayout.html внутрь тэга body:
<div id="btn1-ct"></div>

Это пустой div который послужит как контейнер для кнопки. Затем добавим парочку строк в applayout.js и вот что получится:

/**
* Application Layout
* by Jozef Sakalos, aka Saki
* http://extjs.com/learn/Tutorial:Application_Layout_for_Beginners
*/

 
// reference local blank image
Ext.BLANK_IMAGE_URL = '../extjs/resources/images/default/s.gif';
 
// create namespace
Ext.namespace('myNameSpace');

 
// Just to allow this tutorial to work for 1.1 and 2.
Ext.Ext2 = (Ext.version && (Ext.version.indexOf("2") == 0));

 
// create application
myNameSpace.app = function() {
// do NOT access DOM from here; elements don't exist yet
 

// private variables
var btn1;
var privVar1 = 11;
 
// private functions
var btn1Handler = function(button, event) {

alert('privVar1=' + privVar1);
alert('this.btn1Text=' + this.btn1Text);
};

 
// public space
return {
// public properties, e.g. strings to translate
btn1Text: 'Button 1',
 

// public methods
init: function() {
if (Ext.Ext2) {

btn1 = new Ext.Button({
renderTo: 'btn1-ct',
text: this.btn1Text,
handler: btn1Handler
});
} else {

btn1 = new Ext.Button('btn1-ct', {
text: this.btn1Text,
handler: btn1Handler
});
}

}
};
}(); // end of app
 
// end of file


Переменные btn1 и privVar1 - закрытые, private. Это значит что к ним можно получить доступ только из приложения а вне его они невидимы. Тоже самое справедливо для частной функции btn1Handler.
С другой стороны, btn1Text это публичная public переменная которая видима и может быть доступна или изменена извне приложения (мы продемонстрируем это позже).
Функция init привелигирована в том что может работать как с public так и с private переменными и функциями. В нашем примере она работает с публичной переменной this.btn1Text, а также с private функцией btn1Handler. Функция init публичная (public) и может быть вызвана откуда угодно.
Ок, давайте запустим его. Видите кнопку в верхнем левом углу? Хорошо. Нажмите ее. Вы получите сообщение privVar1=11, так? Частная private функция может работать с частными private переменными.
Однако что то не так при втором показе сообщения и мы видим что this.btn1Text=undefined; это не то, что мы ожидали. Причина в том, что переменная this внутри обработчика теперь указывает на кнопку а не на наше приложение. Добавьте scope:this в конфигурацию кнопки вот так:

if (Ext.Ext2) {
btn1 = new Ext.Button({

renderTo: 'btn1-ct',
text: this.btn1Text,
handler: btn1Handler,
scope:this
});
} else {

btn1 = new Ext.Button('btn1-ct', {
text: this.btn1Text,
handler: btn1Handler,
scope:this

});
}


и перезагрузите страницу. Работает теперь?

Перезагрузка (перекрытие) публичных переменных.
Добавьте следующий код в конце applayout.js чтобы было так:
Ext.apply(myNameSpace.app, {
btn1Text:'Taste 1'
});
 
// end of file


Что теперь делает весь этот код? Сначала он создает объект нашего приложения и затем изменяет (перезагружает) значение публичной переменной btn1Text. Запустите его, чтобы увидеть что текст на кнопке изменился.
Хорошая практика кодирования это хранить весь текст используемый приложением в публичных переменных, так они могут быть легко переведены на другие языки без изменения оригинального кода приложения.
Конечно, следует хранить также другие значения, такие как размеры по-умолчанию, стили и другие настраиваемые опции в публичных переменных. Тогда вам не понадобится исследовать тысячи строк кода приложения чтобы изменить цвет какого нибудь элемента например.


Перегрузка (перекрытие) публичных функций

Давайте изменим наш код на следующий:

Ext.apply(myNameSpace.app, {
btn1Text:'Taste 1',
init: function() {

try {
btn1 = new Ext.Button('btn1-ct', {
text: this.btn1Text,
handler: btn1Handler,
scope: this

});
}
catch(e) {
alert('Error: "' + e.message + '" at line: ' + e.lineNumber);
}

}
});
 
// end of file


Что мы здесь сделали? Просто переопределили функцию init тем же кодом что и до того, только заключенным в блок try/catch чтобы мы могли обрабатывать любые ошибки. Должны мы ожидать прежнего результата или другого? Запустите и увидите.
Да, будет ошибка о том что btn1Handler не определен. Это потому, что обновленная функция пытается получить доступ к частной переменной но не может. Вы видите, оригинальный init привелоигирован и имеет доступ к частному пространству но новый не имеет. В этом есть аспект безопасности: Никакой внешний код не должен получать доступ к частному пространству даже если пытается перегрузить привелигированные методы.