JavaScript для… настольного IP телефона?

// Lets include the app (for standard settings option): var app = require ('app');

// And initialize it as required: app.init ();

// Our select tool input: var SelectInput = require ('SelectInput').SelectInput;

// Include some utilities: var util = require ('util');

// Screen instance: var screen = require ('screen');

// Idle window reference; var wnd = null;

// On foreground event call: var onforeground_called = false;

// On foreground event call to display error: var onforeground_error = false;

// If app is already running it has true value: var instantiated = false;

// Default city for «no-config» app: var DEF_CITY_ID = 27612;

// Screen update interval (for 4 different forecasts) var DEF_FORECAST_SCREEN_INT = 15000;

// Server request interval (to update weather data): var DEF_FORECAST_UPDATE_INT = 3600000;

// Server request interval (to update weather data in case of error): var DEF_FORECAST_ERROR_UPDATE_INT = 60000;

// App runtime configuration: var config = {};

// Runtime gathered weather data: var weather_data = {};

// Which forecast should display next refresh; var forecast_display = 0;

// Count of available forecasts: var forecasts_count = 0;

// Timer id of weather update request timeout var timeout_request = null;

// Timer id of forecast display refresh timeout var timeout_refresh = null;

// Extended time of day explanations: var tod_extended = { '0': ['Утром', 'Днём', 'Вечером'], '1': ['Днём', 'Вечером', 'Ночью'], '2': ['Вечером', 'Ночью', 'Завтра утром'], '3': ['Ночью', 'Завтра утром', 'Завтра днём'] };

// Weekday russian shortcuts (Gismeteo format): var week_days = { '1':'вс', '2':'пн', '3':'вт', '4':'ср', '5':'чт', '6':'пт', '7':'сб' };

// Gismeteo wind direction map: var wind_dir = { '0':'C', '1':'СВ', '2':'В', '3':'ЮВ', '4':'Ю', '5':'ЮЗ', '6':'З', '7':'СЗ' };

// Decode CP1251 (name of city): function decode1251 (str) { var i, result = '', l = str.length, cc;

for (i = 0; i < l; i++) { cc = str.charCodeAt(i);

if ('\r' == str[i]) continue;

if (cc < 192) { if (168 == cc) result += 'Ё'; else if (184 == cc) result += 'ё'; else result += String.fromCharCode(cc); } else result += String.fromCharCode(cc + 848); }

return result; }

// Just a little workaround for IDE: function endForegroundMonitor () { onforeground_called = false; onforeground_error = false; }

// Show window for selecting user-defined city: function showFormCities () { if (! onforeground_called) return;

digium.event.stopObserving ({'eventName' : 'digium.app.background'});

digium.event.observe ({ 'eventName' : 'digium.app.background', 'callback' : function () { endForegroundMonitor (); digium.event.stopObserving ({'eventName' : 'digium.app.background'}); } });

screen.clear ();

var i, select_options = [];

for (i in cities) { if (cities.hasOwnProperty (i)) select_options.push ({'value': i,'display': cities[i]}); }

var select = new SelectInput ({ 'options': select_options, 'width': window.w — 120, 'title':'Выберите город из списка', 'x':100, 'y':20+Text.LINE_HEIGHT, 'initialValue': config['CITY_ID'].toString () });

select.onFocus = function (){return true};

window.add (screen.setTitleText ({'title' : 'Настройка местоположения'})); window.add (new Text (20, 20+Text.LINE_HEIGHT, 70, Text.LINE_HEIGHT, 'Ваш город:')); window.add (select.textWidget);

select.takeFocus ();

select.textWidget.setSoftkey (4, 'Назад', showAppWindow);

select.textWidget.setSoftkey (1, 'Готово', function (){ config['CITY_ID'] = parseInt (select.value); confirmUserCity (); updateWeatherData (); digium.background (); }); }

// Saving user’s choice: function confirmUserCity () { try { digium.writeFile ( 'nv', 'settings.json', JSON.stringify ({'CITY_ID': config['CITY_ID']}) ); } catch (e) {} }

// Getting app-level configuration: function getApplicationConfig () { return util.defaults (app.getConfig ().settings, { 'CITY_ID': DEF_CITY_ID, 'FORECAST_SCREEN_INT': DEF_FORECAST_SCREEN_INT, 'FORECAST_UPDATE_INT': DEF_FORECAST_UPDATE_INT }); }

// Getting user-level configuration: function getLocalConfig () { var result;

try { var configFile = digium.readFile ('nv', 'settings.json');

result = JSON.parse (configFile); } catch (e) { result = {}; }

return result; }

// Function-helper for parsing single node attrs: function getMappedAttributes (node, map) { var i, result = {};

for (i in map) { if (map.hasOwnProperty (i)) result[map[i]] = node.getAttribute (i); }

return result; }

// Parse xml weather data to object: function parseWeatherData (src) { var data = {}, nodes, node, l, i, subnode;

var parser = new DOMParser ();

try { var doc = parser.parseFromString (src, 'application/xml');

nodes = doc.getElementsByTagName ('TOWN');

data.city = decode1251(unescape (nodes[0].getAttribute ('sname'))).replace ('+', ' ');

nodes = doc.getElementsByTagName ('FORECAST');

l = nodes.length; i = 0;

forecasts_count = l;

node = nodes[0];

data.day = node.getAttribute ('day'); data.month = node.getAttribute ('month'); data.weekday = node.getAttribute ('weekday'); data.tod = node.getAttribute ('tod'); data.forecasts = {};

var forecast, tmp;

do { forecast = {};

subnode = node.getElementsByTagName ('PHENOMENA')[0];

forecast = util.defaults (forecast, getMappedAttributes (subnode, { 'cloudiness':'clouds', 'precipitation':'precip', 'rpower':'rpower', 'spower':'spower' }));

subnode = node.getElementsByTagName ('PRESSURE')[0];

tmp = getMappedAttributes (subnode, { 'min':'min', 'max':'max' });

forecast.press = tmp.min + '-' + tmp.max;

subnode = node.getElementsByTagName ('TEMPERATURE')[0];

tmp = getMappedAttributes (subnode, { 'min':'min', 'max':'max' });

forecast.temp = parseInt ((parseInt (tmp.min) + parseInt (tmp.max)) / 2);

subnode = node.getElementsByTagName ('WIND')[0];

tmp = getMappedAttributes (subnode, { 'min':'min', 'max':'max', 'direction':'wdir' });

forecast.wspeed = parseInt ((parseInt (tmp.min) + parseInt (tmp.max)) / 2); forecast.wdir = tmp.wdir;

subnode = node.getElementsByTagName ('RELWET')[0];

forecast = util.defaults (forecast, getMappedAttributes (subnode, { 'max':'hum' }));

data.forecasts[i] = forecast;

node = nodes[++i]; } while (i < l); } catch(e) { data = {error:true}; }

return data; }

// Currently disabled (show error message in window of the app) /*function reportError (info) { endForegroundMonitor ();

onforeground_error = true;

digium.foreground ();

screen.clear ();

window.add (new Text (0, 0, window.w, Text.LINE_HEIGHT * 2, info.type + ': ' + info.description));

digium.event.stopObserving ({'eventName' : 'digium.app.background'});

digium.event.observe ({ 'eventName' : 'digium.app.background', 'callback' : function () { endForegroundMonitor (); digium.event.stopObserving ({'eventName' : 'digium.app.background'}); } }); }*/

// Get current no of forecast to display: function getForecastToDisplay () { if (forecast_display > (forecasts_count — 1)) return forecast_display = 0; else return forecast_display++; }

// Get labels values (with updated data): function getUpdatedLabels (forecast_no) { var result = {}, time = '';

var forecast = weather_data.forecasts[forecast_no];

if (! forecast_no) time = 'Сейчас'; else time = tod_extended[weather_data.tod][forecast_no — 1];

result.temp = time + ' ' + forecast.temp + ' °C'; result.press = forecast.press + ' мм рт. ст.'; result.hum = 'Влажность ' + forecast.hum + '%'; result.wind = 'Ветер ' + forecast.wspeed + ' м/с ' + wind_dir[forecast.wdir];

return result; }

// «Humanize» Gismeteo phenomena: function getPhenomenaObj (forecast, nt) { // Default state of the phenomena part: var result = {icon:'unknown', status:'Нет данных'};

if ((1 == forecast.spower) && ((8 == forecast.precip) || (9 == forecast.precip))) {

result.icon = 'storm'; result.status = 'Грозы'; } else if (5 == forecast.precip) { result.icon = 'rainfall'; result.status = 'Ливень'; } else if (4 == forecast.precip) { result.icon = 'rain'; result.status = 'Дождь'; } else if ((6 == forecast.precip) || (7 == forecast.precip)) { result.icon = 'snow'; result.status = 'Снег'; } else if (3 == forecast.clouds) { result.icon = 'mostlycloudy'; result.status = 'Пасмурно'; } else if (2 == forecast.clouds) { result.icon = 'cloudy'; result.status = 'Облачно'; } else if ((1 == forecast.clouds) && (10 == forecast.precip)) { if (nt) result.icon = 'nt_mostlyclear'; else result.icon = 'mostlyclear';

result.status = 'Перем. облач.'; } else if ((0 == forecast.clouds) && (10 == forecast.precip)) { if (nt) result.icon = 'nt_clear'; else result.icon = 'clear';

result.status = 'Ясно'; }

return result; }

// Refresh widget contents on the idle display: function idleRefresh (on_timer) { var labels, fno, f, nt = false;

if (util.isDef (on_timer) && on_timer) { fno = getForecastToDisplay (); labels = getUpdatedLabels (fno); } else { forecast_display = 0; fno = forecast_display++; labels = getUpdatedLabels (fno); }

f = weather_data.forecasts[fno];

if (weather_data.tod == 0 && fno == 0) nt = true; else if (weather_data.tod == 1 && fno == 3) nt = true; else if (weather_data.tod == 2 && fno == 2) nt = true; else if (weather_data.tod == 3 && fno == 1) nt = true;

var phen = getPhenomenaObj (f, nt);

wnd[0] = new Image ('app', phen.icon + '.gif', 15, Text.LINE_HEIGHT, 50, 50); wnd[2].label = labels.temp; wnd[3].label = phen.status; wnd[4].label = labels.press; wnd[5].label = labels.hum; wnd[6].label = labels.wind;

clearTimeout (timeout_refresh);

timeout_refresh = setTimeout (function (){ idleRefresh (true); }, config['FORECAST_SCREEN_INT']); }

// Function to finalize «get weather request» function getWeatherCb (data) { if (data.hasOwnProperty ('error') && data.error) { wnd[1].label = 'Обновление данных…'; setTimeout (updateWeatherData, DEF_FORECAST_ERROR_UPDATE_INT); } else { weather_data = data; // overwrite previous data

wnd[1].label = data.city + ', ' + week_days[data.weekday] + ' ' + data.day + '.' + data.month;

idleRefresh (); } }

// Get weather data from Gismeteo: function getWeather (cb) { wnd[1].label = 'Обновление данных…';

var uri = 'http://informer.gismeteo.ru/xml/' + config['CITY_ID'] + '.xml';

var request = new NetRequest ();

request.open ('GET', uri, true);

request.onreadystatechange = function () { if (4 == request.readyState) { if (200 == request.status) cb (parseWeatherData (request.responseText)); else { setTimeout (updateWeatherData, DEF_FORECAST_ERROR_UPDATE_INT); } } };

request.send ();

clearTimeout (timeout_request);

timeout_request = setTimeout ( updateWeatherData, config['FORECAST_UPDATE_INT'] ); }

// Function for usage in setTimeout func: function updateWeatherData () { getWeather (getWeatherCb); }

// Idle window initialization and reference store; function initialize () { var cursor = 0, label;

wnd = digium.app.idleWindow;

wnd.add (new Image ('app', 'unknown.gif', 15, Text.LINE_HEIGHT, 50, 50));

for (var i = 0; i < 6; i++) { if (0 == i) label = new Text(0, Text.LINE_HEIGHT * cursor++, wnd.w, Text.LINE_HEIGHT, 'Получение данных...'); else if (2 == i) label = new Text(10, 50, 65, Text.LINE_HEIGHT, 'Нет данных'); else label = new Text(65, Text.LINE_HEIGHT * cursor++, wnd.w - 65, Text.LINE_HEIGHT);

label.align (Widget.CENTER);

wnd.add (label); }

digium.app.exitAfterBackground = false;

wnd.hideBottomBar = true;

digium.event.observe ({ 'eventName' : 'digium.app.start', 'callback' : function () { setTimeout (function (){instantiated = true;}, 1000); } });

digium.event.observe ({ 'eventName' : 'digium.app.idle_screen_show', 'callback' : function () { if (digium.app.idleWindowShown) idleRefresh (); } });

digium.event.observe ({ 'eventName' : 'digium.app.foreground', 'callback' : function () { // Stopping recursive call when error message box // should be shown by calling digium.foreground () if (onforeground_called) return;

onforeground_called = true;

if (! instantiated) { // bring app to idle on the first launch: digium.background (); instantiated = true; endForegroundMonitor (); } else { // Show select options list: 1. setting up; 2. exit widget // showFormCities (); showAppWindow (); } } }); }

function showAppWindow () { if (! onforeground_called) return;

digium.event.stopObserving ({'eventName' : 'digium.app.background'});

digium.event.observe ({ 'eventName' : 'digium.app.background', 'callback' : function () { endForegroundMonitor (); digium.event.stopObserving ({'eventName' : 'digium.app.background'}); } });

screen.clear ();

window.add (screen.setTitleText ({'title' : 'Погода'}));

try { window.add (new Text (4, 20, window.w, Text.LINE_HEIGHT, 'Выберите опцию:'));

var menuObj = new List (4, 20 + Text.LINE_HEIGHT, window.w, window.h);

menuObj.setProp ('cols', 2).setProp ('rows', 2);

menuObj.set (0, 0, '1.'); menuObj.set (0, 1, 'Выбрать город'); menuObj.set (1, 0, '2.'); menuObj.set (1, 1, 'Закрыть виджет');

menuObj.setColumnWidths (15, 0); menuObj.select (0);

menuObj.onFocus = function () {return true; };

var selected = function () { if (0 == menuObj.selected) showFormCities (); else { digium.app.exitAfterBackground = true; digium.background (); } };

menuObj.onkey1 = function (){ menuObj.select (0); selected (); }; menuObj.onkey2 = function (){ menuObj.select (1); selected (); };

menuObj.onkeyselect = selected;

menuObj.setSoftkey (1, 'OK', selected);

window.add (menuObj);

menuObj.takeFocus (); } catch (e) { window.add (new Text (0, 20, window.w, Text.LINE_HEIGHT, e.message)); } }

// Get summary configuration data with user-level priority: config = util.defaults (getLocalConfig (), getApplicationConfig ());

// Initialize: initialize ();

// Start app main cycle: updateWeatherData ();

© Habrahabr.ru