Статьи

Создание ориентированного на местоположение сайта с помощью Sencha Touch

Конечный продукт
Что вы будете создавать

Из этого туториала Вы узнаете, как разработать мобильный сайт на основе определения местоположения с помощью поисковой системы Google Place и Sencha Touch 2.1. Это учебное пособие, состоящее из двух частей, и в этой первой части мы узнаем, как создать проект с помощью Sencha cmd, создать классную тему с помощью SASS / Compass и найти службы рядом с местоположением пользователя.


Google предоставляет набор API для поиска различных сервисов по типам и местоположению пользователя. В настоящее время Google поддерживает в общей сложности 96 типов услуг. Есть еще 30 сервисов, которые можно получить только через поиск. У Google есть полный список из них .

Чтобы получить доступ к API Адресов, основной целью является регистрация приложения в консоли API Google . Как только мы аутентифицируемся, мы получим ключ API, который требуется для каждого запроса API. У Google есть пошаговое руководство .

Google Places API использует ключ API для идентификации вашего приложения. Ключи API управляются через Консоль API Google . Вам понадобится ваш собственный ключ API, прежде чем вы сможете начать использовать API. Чтобы активировать API Адресов и создать свой ключ:

  1. Посетите консоль API по адресу https://code.google.com/apis/console и войдите в свою учетную запись Google.
  2. Проект по умолчанию, который называется API Project , создается для вас при первом входе в консоль API. Вы можете использовать проект или создать новый, нажав кнопку « Проект API» в верхней части окна и выбрав « Создать» . Клиенты Maps API для бизнеса должны использовать созданный для них проект API в рамках своей покупки в Places for Business.
  3. Нажмите на ссылку Услуги в левом меню.
  4. Нажмите переключатель состояния рядом с записью API Адресов . Переключатель переходит в положение « Вкл .».
  5. Нажмите API доступа из левой навигации. Ваш ключ указан в разделе Простой доступ к API .

Я предполагаю, что у вас есть локальный сервер и настройка Sencha завершена. Если нет, просмотрите подробную документацию здесь со всеми шагами. Мы генерируем приложение Locator, используя эту команду на нашем локальном сервере.

1
sencha -sdk /path/to/sdk generate app Locator c:/xampp/htdocs/locator

Как только мы закончим, мы откроем приложение в браузере с URL http: // localhost / locator и увидим простое приложение с вкладками.


Теперь нам нужно структурировать приложение с помощью компонентов MVC.

Контроллеры

  1. App.js

Взгляды

  1. Main.js
  2. Categories.js
  3. PlaceList.js

магазины

  1. Categories.js
  2. Places.js

модели

  1. категория
  2. Место

Приложение Sencha может иметь несколько файлов контроллера. Тем не менее, для небольшого приложения, подобного этому, один контроллер будет в порядке. Мы будем хранить все привязки события и соответствующие функциональные возможности внутри этого контроллера.

Представления представляют страницы приложения.

  • Главный вид работает как родитель всех видов.
  • Категории будут перечислены все услуги, которые Google поддерживает.
  • Представление PlaceList покажет список всех мест рядом с местоположением пользователя и на основе определенного сервиса.

Поскольку у нас есть два списка, мы поддерживаем две модели: Категория и Место . Аналогично, две категории и места хранения необходимы для извлечения и сохранения связанных данных. Нам нужно добавить все эти детали компонентов в app.js, чтобы движок Sencha мог их загрузить при запуске.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
Ext.Loader.setPath({
  ‘Ext’: ‘touch/src’,
  ‘Locator’: ‘app’
});
 
Ext.application({
  name: ‘Locator’,
 
  requires: [
    ‘Ext.MessageBox’,
    ‘Locator.util.Util’],
 
  views: [
    ‘Main’,
    ‘Categories’,
    ‘PlaceList’],
 
  controllers: [‘App’],
 
  models: [‘Category’, ‘Place’],
 
  stores: [‘Categories’, ‘Places’],
 
  icon: {
    ’57’: ‘resources/icons/Icon.png’,
    ’72’: ‘resources/icons/Icon~ipad.png’,
    ‘114’: ‘resources/icons/Icon@2x.png’,
    ‘144’: ‘resources/icons/Icon~ipad@2x.png’
  },
 
  isIconPrecomposed: true,
 
  startupImage: {
    ‘320×460’: ‘resources/startup/320×460.jpg’,
    ‘640×920’: ‘resources/startup/640×920.png’,
    ‘768×1004’: ‘resources/startup/768×1004.png’,
    ‘748×1024’: ‘resources/startup/748×1024.png’,
    ‘1536×2008’: ‘resources/startup/1536×2008.png’,
    ‘1496×2048’: ‘resources/startup/1496×2048.png’
  },
 
  launch: function () {
    // Destroy the #appLoadingIndicator element
    Ext.fly(‘appLoadingIndicator’).destroy();
 
    // Initialize the main view
    Ext.Viewport.add(Ext.create(‘Locator.view.Main’));
  },
 
  onUpdated: function () {
    Ext.Msg.confirm(
      «Application Update»,
      «This application has just successfully been updated to the latest version. Reload now?»,
 
    function (buttonId) {
      if (buttonId === ‘yes’) {
        window.location.reload();
      }
    });
  }
});

Для каждого приложения нам нужен набор общих функций и свойств, которые будут использоваться во всем приложении. Для этого мы создаем синглтон-класс Util и помещаем файл в каталог app / util / . Вам не нужно понимать функции этого файла в настоящее время. Мы будем продолжать обсуждать эти функции по мере продвижения вперед.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
Ext.define(‘Locator.util.Util’, {
  singleton: true,
  // Whether the application views will have a animation while changing on=r not
  enablePageAnimations: true,
  // User’s current location is saved here
  userLocation: null,
  // Google place api key
  API_KEY: ‘AIzaSyBmbmtQnXfq22RJhJfitKao60wDgqrC5gA’,
 
  // All the api urls
  api: (function () {
    //var baseUrl = ‘https://maps.googleapis.com/maps/api/place/’;
    var baseUrl = ‘php/action.php’;
    return {
      baseUrl: baseUrl,
      categories: ‘resources/data/categories.json’,
      nearestPlaces: baseUrl + »,
      nearBySearch: ‘nearbysearch’,
      photo: ‘photo’,
      details: ‘details’
    }
  })(),
 
  // Destroy a Sencha view
  destroyCmp: function (child, parent) {
    parent = parent ||
 
    if (child) {
      Ext.defer(function () {
        parent.remove(child);
      }, Locator.util.Util.animDuration);
    }
  },
 
  // Show general message alert
  showMsg: function (msg, title, cb, scope) {
    if (msg) {
      Ext.Msg.alert(title || ‘Error’, msg.toString(), cb || function () {}, scope || window);
    }
 
    return this;
  },
 
  // Animate the active item
  showActiveItem: function (parentPanel, childPanel, animation) {
    animation = Ext.apply({
      type: ‘slide’,
      duration: LocatrConfig.amimationDuration
    }, animation ||
 
    if (parentPanel && childPanel) {
      if (this.enablePageAnimations && animation && animation.type) {
        parentPanel.animateActiveItem(childPanel, animation);
      } else {
        parentPanel.setActiveItem(childPanel);
      }
    }
 
    return this;
  },
 
  // Show a loading box on a
  showLoading: function (panel, doShow, message) {
    panel = panel ||
    if (panel) {
      if (doShow) {
        panel.setMasked({
          xtype: ‘loadmask’,
          message: message ||
        });
      } else {
        panel.setMasked(false);
      }
    }
 
    return this;
  },
 
  // Capitalize first character of each word of a string
  toTitleCase: function (str) {
    if (!str) {
      return »;
    }
 
    return str.replace(/\w\S*/g, function (txt) {
      return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
    });
  }
 
});

Мы настроили Главный вид, который является оболочкой для всех видов. Мы используем Navigation View для того же самого, что очень полезно для простого макета карты и управления кнопками назад. При запуске он имеет только список категорий в качестве дочернего.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
/**
 * Main view — holder of all the views.
 * Card layout by default in order to support multiple views as items
 */
Ext.define(‘Locator.view.Main’, {
  extend: ‘Ext.NavigationView’,
  xtype: ‘main’,
  config: {
    cls: ‘default-bg’,
    items: [{
      xtype: ‘categories’
    }]
  }
});

Теперь настройка приложения завершена. У нас есть ключ API Google Адресов, и мы готовы создать список всех типов и показать его на главной странице. Хотя есть проблема. Google не предоставляет API для получения всех этих типов. Мы должны вручную создать файл данных со списком всех типов. Я создал файл json под названием category.json, в котором перечислены все доступные типы, и поместил его в каталог resources / data .

1
{categories:[{type:»accounting»},{type:»airport»},{type:»amusement_park»},{type:»aquarium»},{type:»art_gallery»},{type:»atm»},{type:»bakery»},{type:»bank»},{type:»bar»},{type:»beauty_salon»},{type:»bicycle_store»},{type:»book_store»},{type:»bowling_alley»},{type:»bus_station»},{type:»cafe»},{type:»campground»},{type:»car_dealer»},{type:»car_rental»},{type:»car_repair»},{type:»car_wash»},{type:»casino»},{type:»cemetery»},{type:»church»},{type:»city_hall»},{type:»clothing_store»},{type:»convenience_store»},{type:»courthouse»},{type:»dentist»},{type:»department_store»},{type:»doctor»},{type:»electrician»},{type:»electronics_store»},{type:»embassy»},{type:»establishment»},{type:»finance»},{type:»fire_station»},{type:»florist»},{type:»food»},{type:»funeral_home»},{type:»furniture_store»},{type:»gas_station»},{type:»general_contractor»},{type:»grocery_or_supermarket»},{type:»gym»},{type:»hair_care»},{type:»hardware_store»},{type:»health»},{type:»hindu_temple»},{type:»home_goods_store»},{type
01
02
03
04
05
06
07
08
09
10
11
12
13
14
Ext.define(‘Locator.model.Category’, {
  extend: ‘Ext.data.Model’,
  config: {
    fields: [
      «type», {
      name: «name»,
      type: «string»,
      convert: function (v, record) {
        // Converts to title case and returns
        return Locator.util.Util.toTitleCase(record.get(‘type’).split(‘_’).join(‘ ‘));
      }
    }, «size»]
  }
});

Свойство «name» этой модели использует то же значение «type» категории. Так как большинство типов имеют «подчеркивание», эта функция преобразования создает значение, пропуская «_» и преобразуя строку в регистр заголовка. Итак, «travel_agency» становится « Туристическим агентством », и мы сохраняем его под именем свойства этой модели.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
Ext.define(‘Locator.store.Categories’, {
  extend: ‘Ext.data.Store’,
  config: {
    model: ‘Locator.model.Category’,
    autoLoad: true,
    sorters: ‘name’,
    grouper: {
      groupFn: function (record) {
        return record.get(‘name’)[0];
      }
    },
    proxy: {
      type: ‘ajax’,
      url: Locator.util.Util.api.categories,
      reader: {
        type: ‘json’,
        rootProperty: ‘categories’
      }
    }
  }
});

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

Представление категории представляет собой простой список. Мы используем indexBar и сгруппированные функциональные возможности для легкого доступа ко всем типам.

01
02
03
04
05
06
07
08
09
10
11
12
Ext.define(‘Locator.view.Categories’, {
  extend: ‘Ext.List’,
  xtype: ‘categories’,
  config: {
    cls: ‘default-bg category-list’,
    itemTpl: ‘{name}’,
    store: ‘Categories’,
    grouped: true,
    indexBar: true,
    title: Lang.home
  }
});

Список выглядит так:



Мы можем добавить определенные наборы уже существующих переменных, чтобы изменить существующую тему Sencha и получить новый внешний вид. Ниже приведен файл SASS. Если вы еще не выполнили настройку SASS, следуйте этому посту в блоге, чтобы получить пошаговое руководство.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// Basic color definitions
$base-color: #333;
$base-gradient: ‘matte’;
$active-color: #36B8FF;
 
// Toolbar styles
$toolbar-base-color: #444;
 
// List styles
$list-header-bg-color : #ABE2FF;
 
@import ‘sencha-touch/default/all’;
 
// You may remove any of the following modules that you
// do not use in order to create a smaller css file.
@include sencha-panel;
@include sencha-buttons;
@include sencha-sheet;
@include sencha-picker;
@include sencha-tabs;
@include sencha-toolbar;
@include sencha-toolbar-forms;
@include sencha-indexbar;
@include sencha-list;
@include sencha-layout;
@include sencha-carousel;
@include sencha-form;
@include sencha-msgbox;
@include sencha-loading-spinner;
@include sencha-list-pullrefresh;

Мы меняем цвет верхней панели инструментов и цвет заголовка списка, а также добавляем плагин списка.


После того, как мы нажмем один из элементов категории, мы захотим просмотреть все предприятия, расположенные рядом с текущим местоположением пользователя в этой категории. Мы должны следовать этому набору задач:

  1. Получить текущее местоположение пользователя с помощью GeoLocation API
  2. С широтой и долготой отправьте запрос в Google API для получения данных
  3. Показать страницу списка мест

Мы можем либо напрямую использовать функцию геолокации навигатора, либо использовать Sencha Ext.device.Geolocation . Мы сохраняем широту и долготу в экземпляре Util для будущего использования.

1
2
3
4
5
6
7
8
Ext.device.Geolocation.getCurrentPosition({
  success: function (position) {
    me.util.userLocation = position.coords.latitude + ‘,’ + position.coords.longitude;
  },
  failure: function () {
    me.util.showMsg(Lang.locationRetrievalError);
  }
});

Google Places API пока не поддерживает запросы JSONP , поэтому мы не сможем получить данные непосредственно со стороны клиента. Мы должны использовать прокси-сервер для получения данных. Эта проблема может быть решена с помощью PHP и cURL.

Файл Config содержит несколько констант. Мы устанавливаем базовый URL API, тип вывода данных и размер изображения.

1
2
3
4
define(«BASE_API_URL», «https://maps.googleapis.com/maps/api/place/»);
define(«DATA_OUTPUT_TYPE», «json»);
define(«IMAGE_MAX_HEIGHT», 500);
define(«IMAGE_MAX_WIDTH», 500);

Это класс PhP, который содержит функциональные возможности для настройки URL, отправки запросов cURL и извлечения данных.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
class Locatr {
 
  /**
   * Sets up the url according to passed parameters
   * @return String A complete url with all the query strings
   */
  private static function getFinalUrl() {
    return html_entity_decode(BASE_API_URL.$_REQUEST[«action»].
    «/».DATA_OUTPUT_TYPE.
    «?».$_SERVER[‘QUERY_STRING’]);
  }
 
  /**
   * A generic function to send all the cURL requests
   * @return String Response for that cURL request
   */
  private static function sendCurlRequest() {
    // Get cURL resource
    $curl = curl_init();
 
    // Set some options — we are passing in a useragent too here
    curl_setopt_array($curl, array(
    CURLOPT_RETURNTRANSFER = > 1,
    CURLOPT_URL = > self::getFinalUrl(),
    CURLOPT_SSL_VERIFYPEER = > false,
    CURLOPT_USERAGENT = > ‘Codular Sample cURL Request’));
 
    // Send the request & save response to $resp
    $response = curl_exec($curl);
 
    // Close request to clear up some resources
    curl_close($curl);
 
    return $response;
  }
 
  /**
   * Retrieves all the nearby places and one image of each if available
   * @return String Returns all the places in json
   */
  public static function getNearBySearchLocations() {
    try {
      $data = json_decode(self::sendCurlRequest());
      $item = «»;
 
      for ($i = 0; $i < count($data -> results); $i++) {
        $item = $data -> results[$i];
        if (isset($item -> photos)) {
          $imageUrl = BASE_API_URL.
          «photo?photoreference=».$item -> photos[0] -> photo_reference.
          «&sensor=false&maxheight=300&maxwidth=300&key=».$_GET[«key»];
          $data -> results[$i] -> photos[0] -> url = $imageUrl;
        }
      }
 
      return json_encode($data);
    } catch (Exception $e) {
      print «Error at getNearBySearchLocations : «.$e -> getMessage();
    }
  }
}

Вот функциональность каждого метода в этом классе:

  1. getFinalUrl : Это устанавливает полный URL с базовым URL, типом данных ответа и строками запроса, отправленными со стороны клиента. Мы вызываем эту функцию из файла action.php .
  2. sendCurlRequest: это базовый запрос cURL GET для получения данных. Вы также можете использовать метод file_get_contents() чтобы получить данные здесь.
  3. getNearBySearchLocations: извлекает данные из API Google для связанного типа в пределах определенного радиуса. Однако есть хитрость: Google не передает фотографии компании с этими данными. Вместо этого они отправляют ссылки на изображения. Вам нужно создать URL с высотой, шириной изображения, ключом API и ссылкой на фотографию, чтобы получить это изображение.

    Этот URL создается с первой ссылкой на изображение и передается с данными ответа для каждого места. Это помогает нам показать хотя бы одно изображение, доступное для каждого бизнеса.

    Этот файл просто используется для вызова функции getNearBySearchLocations класса Locator. Мы отправляем запросы AJAX со стороны клиента прямо в этот файл.

    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    include_once ‘config.php’;
    include_once ‘Locatr.php’;
     
    $action = $_REQUEST[«action»];
     
    if (!isset($action)) {
      throw new Exception(«‘action’ parameter is not supplied»);
    }
     
    switch ($action) {
      case «nearbysearch»:
        print Locatr::getNearBySearchLocations();
        break;
    }

    Для списка мест нам нужен магазин и модель, похожая на список категорий.

    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    Ext.define(‘Locator.model.Place’, {
      extend: ‘Ext.data.Model’,
      config: {
        fields: [
          «formatted_address»,
          «geometry»,
          «icon»,
          «id»,
          «name»,
          «rating»,
          «reference»,
          «types»,
          «vicinity»,
          «photos»]
      }
    });
    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    Ext.define(‘Locator.store.Places’, {
      extend: ‘Ext.data.Store’,
      config: {
        model: ‘Locator.model.Place’,
        proxy: {
          type: ‘ajax’,
          url: Locator.util.Util.api.nearestPlaces,
          reader: {
            type: ‘json’,
            rootProperty: ‘results’
          }
        }
      }
    });

    До сих пор нам не требовался контроллер для какой-либо функциональности, поскольку список категорий заполнялся автоматически его хранилищем. Теперь нам нужен контроллер для обработки событий. Мы перечислим все необходимые компоненты в свойстве контроллера refs.

    1
    2
    3
    4
    5
    refs: {
      categoriesList: ‘categories’,
      main: ‘main’,
      placeList: ‘placelist’
    }

    Событие щелчка по списку в элементах управления:

    1
    2
    3
    4
    5
    control: {
      categoriesList: {
        itemtap: ‘loadPlaces’
      }
    }

    При нажатии на категорию, мы хотим показать список мест, доступных в этой категории. Как мы уже говорили ранее, сначала мы получим текущее местоположение пользователя, а затем, с широтой и долготой, отправим запрос ajax в файл action.php. Контроллер с функцией loadPlaces выглядит так:

    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    Ext.define(‘Locator.controller.App’, {
      extend: ‘Ext.app.Controller’,
      requires: [‘Ext.device.Geolocation’, ‘Ext.Map’],
      util: Locator.util.Util,
      config: {
        refs: {
          categoriesList: ‘categories’,
          main: ‘main’,
          placeList: ‘placelist’
        },
     
        control: {
          categoriesList: {
            itemtap: ‘loadPlaces’
          }
        }
      },
     
      /**
       * Retrieve all the places for a particlur category
       */
      loadPlaces: function (list, index, target, record) {
        var me = this,
          loadPlaces = function () {
            // Show the place list page
            me.showPlaceList(record);
     
            // Load the store with user’s location, radius, type and api key
            store.getProxy().setExtraParams({
              location: me.util.userLocation,
              action: me.util.api.nearBySearch,
              radius: me.util.defaultSearchRadius,
              sensor: false,
              key: me.util.API_KEY,
              types: record.get(‘type’)
            });
     
            store.load(function (records) {
              me.util.showLoading(me.getPlaceList(), false);
            });
          },
          store = Ext.getStore(‘Places’);
     
        // If user’s location is already not set, fetch it.
        // Else load the places for the saved user’s location
        if (!me.util.userLocation) {
          Ext.device.Geolocation.getCurrentPosition({
            success: function (position) {
              me.util.userLocation = position.coords.latitude + ‘,’ + position.coords.longitude;
              loadPlaces();
            },
            failure: function () {
              me.util.showMsg(Lang.locationRetrievalError);
            }
          });
        } else {
          // Clean the store if there is any previous data
          store.removeAll();
          loadPlaces();
        }
      },
     
      /**
       * Show place list
       */
      showPlaceList: function (record) {
        this.getMain().push({
          xtype: ‘placelist’,
          title: record.get(‘name’)
        });
      }
    });

    Представление PlaceList также является простым списком. Мы используем XTemplate здесь, чтобы использовать некоторые функции фильтрации. Функция getImage получает изображение бизнеса. Если изображение недоступно, возвращается значок для этой компании.

    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    Ext.define(‘Locator.view.PlaceList’, {
      extend: ‘Ext.List’,
      xtype: ‘placelist’,
      config: {
        cls: ‘default-bg placelist’,
        store: ‘Places’,
        emptyText: Lang.placeList.emptyText,
        itemTpl: Ext.create(‘Ext.XTemplate’,
          ‘{[this.getImage(values)]}’,
          ‘<div class=»item» data-placelistitem-id=»{id}»>’,
          ‘<div class=»name»>{name}</div>’,
          ‘<div class=»vicinity»>{vicinity}</div>’,
          ‘{rating:this.getRating}’,
          ‘</div>’, {
     
          // Returns the business image if available.
          getImage: function (data) {
            if (data.photos && data.photos.length > 0) {
              return ‘<div class=»photo»><img src=»‘ + data.photos[0].url + ‘» /></div>’;
            }
     
            return ‘<div class=»icon-wrapper»><div class=»icon» style=»-webkit-mask-image:url(‘ + data.icon + ‘);»
          },
     
          // Shows a star based rating.
          getRating: function (rating) {
            return Locator.util.Util.getRating(rating);
          }
        })
      }
    });

    Мы получаем рейтинг от нуля до пяти для предприятий. Вместо того, чтобы показывать номер рейтинга, мы можем написать простую функцию, которая показывает рейтинг в виде звездочек Мы добавляем функцию getRating в файл утилит , который можно использовать внутри функций этого шаблона PlaceList:

    Есть три изображения: без звезды, полу-звезды и полной звезды. CSS приведен ниже:

    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    getRating: function (rating, max, hideRatingValue) {
      if (rating !== undefined) {
        var str = ‘<div class=»ratings»>’;
        rating = parseFloat(rating);
        max = max ||
     
        // We divide the rating into a part upto maximum value
     
        for (var i = 1; i < = max; i++) {
          // For each 1 rating, add a full star
          if (i < = rating) {
            str += ‘<div class=»star full-star»></div>’;
          }
     
          if (i > rating) {
            // If the part rating is a decimal between 0 & 1, add half star
            if (rating % 1 !== 0 &&; (i — rating) < 1) {
              str += ‘<div class=»star half-star»></div>’;
            }
            // For all part rating value 0, add no star
            else {
              str += ‘<div class=»star no-star»></div>’;
            }
          }
        }
     
        if (!hideRatingValue) {
          str += ‘<div class=»value»>’ + rating + ‘</div>’;
        }
     
        str += ‘</div>’;
     
        return str;
      }
     
      return Lang.noRating;
    }

    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    .ratings{
        overflow: auto;
    }
     
    .ratings div.star{
        float: left;
        height: 14px;
        width: 14px;
        background-size: 12px !important;
        background-position: 50%;
    }
     
    .ratings .full-star{
        background: url(../images/full_star.png) no-repeat;
    }
     
    .ratings .half-star{
        background: url(../images/half_star.png) no-repeat;
    }
     
    .ratings .no-star{
        background: url(../images/no_star.png) no-repeat;
    }
     
    .ratings .value{
        float: left;
        font-size: 13px;
        font-weight: bold;
        margin-left: 5px;
    }

    Вот окончательный вид PlaceList .


    01
    02
    03
    04
    05
    06
    07
    08
    09
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    /****************************** Place List ******************************/ .placelist.x — list — emptytext {
      font — size: 14px;
      color: #fff;
      padding: 20px;
    }
     
    .x — list.placelist.x — list — item.x — dock — horizontal {
      border: 0!important;
    }
     
    .x — list.placelist.x — list — item.item {
      /* background: rgba(255, 255, 255, 0.8);
                                        font-size: 14px;
                                        padding: 8px;*/
     
      /* background: rgba(255, 255, 255, 0.8);*/
      background: -webkit — gradient(linear, left top, left bottom, color — stop(0 % , #ffffff), color — stop(47 % , #f6f6f6), color — stop(100 % , #ededed));
      background: -webkit — linear — gradient(top, #ffffff 0 % , #f6f6f6 47 % , #ededed 100 % );
      font — size: 14px;
      border — radius: 5px;
      padding: 8px;
      padding — right: 82px;
    }
     
    .x — list.placelist.x — list — item.item.name {
      font — weight: bold;
      margin: 3px 0 8px 0;
    }
     
    .x — list.placelist.x — list — item.item.vicinity {
      font — size: 12px;
      color: #222;
        margin-bottom: 10px;
    }
     
    .x-list.placelist .x-list-item .item .rating{
     
    }
     
    .x-list.placelist .x-list-item .photo,
    .x-list.placelist .x-list-item .icon-wrapper{
        position: absolute;
        display: -webkit-box;
        -webkit-box-align: center;
        -webkit-box-pack: center;
        right: 25px;
        top: 6px;
    }
     
    .x-list.placelist .x-list-item .photo img{
        max-width: 75px;
        max-height: 63px;
        border: 2px solid white;
        -webkit-box-shadow: 0 0 5px 0px rgba(0, 0, 0, 0.5);
        background: black;
    }
     
    .x-list.placelist .x-list-item .icon-wrapper{
        background: # 960000;
      border: 2px solid white;
    }
     
    .x — list.placelist.x — list — item.icon {
      width: 50px;
      height: 50px;
      background: white;
      —
      webkit — mask — size: 35px;
      }
     
      /****************************** Place List ENDS ******************************/
    </code>
     
    <p>We can add a pull-to-refresh plugin to this place list.
     
    <code>
    plugins: [{
      xclass: ‘Ext.plugin.PullRefresh’,
      pullRefreshText: Lang.placeList.pullToRefresh
    }]</pre>
    And because we are using a dark background, we need to change the pull-to-refresh css a bit.
    <pre lang=»css»>/* Pull to refresh plugin */
    .x-list-pullrefresh {
      color: #fff;
    }
     
    .x-list-pullrefresh-arrow {
      -webkit — mask: center center url(data: image / png; base64, iVBORw0KGgoAAAANSUhEUgAAACgAAAA8CAYAAAAUufjgAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAjFJREFUeNrsmU8oREEYwOexdtNuKBfFwdVhCyfuysnFiXISS + 1BLopyUpKLXETkRLaUi1LK3Q2lpPbiQLnIn03a / Hm + z86Ttv0zM++bfbOar36Hbad5v535Zp7v47iuy0wOpyoEHccRHV9L9NxPkUE / bhKCOKiOSPAdn69DsJ5I8E2HYA0QJRJ8Bb50CDYRCT7pEMQD0kwk + CByUFQEW4gE73UIhoA2IsFb4ENEMCQ5MdU1IxwygpT3oKNLMGyyYFVscdhusc8tDpu + xRG7xf95BW0O2kNiV1AgIvaQ2BzUJNgJNJYZGyUU7OG1cal4Bi68oqkDPszy2teEwJp5Cdyu / lZ1g8CwIYJ7wEF + 2YmrNw90Byx3BizgKhaqizEP1wg7CLLxCEzy / CtauMeBlQDyEfNuGrgU6SyM8F9SyVgHdmRaH6tAb4XkToEp2d4M5mOK0TWMigU2koa8vJMRZPxEb2ss2LEVPMpPLlMRxBgDZjQJLgNbxb6Uab9tAn3EcifAeKkBMoLY + j0GWonk7oB + lmsFkwhidAGHBPmIeTcAnJcbKCuIMQEs + hScAzZEBqoIYuzyFVCJI36lMJ2CDfxibZeUu + EX / 4uMIFP8ZyLejxkgK0hG5a8kP4IYSZbr1IuQVHmAX0HGX4VuGfZVJ6cQxPd1uoRcWqDW0SroFVzZAnJZ / h0LWhAjUUAw4XdSSsH8fExRTEgtGAOuOTETBb16Jk412e + bxOSwglYw6PgWYABvLk8P7zGJFwAAAABJRU5ErkJggg == ) no — repeat;
      background: #fff;
    }

    Здесь это идет:



    Это первая часть урока. Мы создали список служб, предоставляемых Google Places API, а затем для определенной службы, и показали список всех близлежащих мест. В следующей и последней части этого руководства мы рассмотрим следующие функции:

    1. Отображение всех мест для категории в Google Maps
    2. Показаны детали каждого места. Это будет включать показ отдельной карты для определенного места, создание мозаичной фотогалереи на основе Sencha, карусель полноэкранных изображений и список отзывов.

    Sencha в настоящее время является одной из самых сильных мобильных библиотек на основе HTML5. После того, как вы его настроите, вы сможете писать отличные, плавные мобильные приложения. Эти приложения могут использоваться как мобильные веб-сайты или помещаться в Phonegap для создания гибридных приложений для iOS и Android.


    Вторая часть этого урока доступна уже сейчас. Найдите его здесь: Создайте сайт с информацией о местоположении с помощью Sencha Touch — Отображение местоположений .