Прежде чем я начну, приведу небольшой отказ от ответственности: то, что я строю здесь, полностью для развлечения и потому что я подумал, что это может быть интересно. Я критикую и совершенствую форму, созданную людьми умнее меня и более чем адекватными для 99,99% пользователей. По сути, я увидел то, что хотел построить, и сделал это.
В настоящее время я являюсь владельцем телефона HTC M8 — мое возвращение в Android после использования iPhone для нескольких версий. Мне нравится пользовательский интерфейс HTC, и в целом телефон был довольно невероятным, но после последнего обновления Android OS мой телефон начал становиться все более и более вялым. Дошло до того, что для отклика телефона потребовалось бы 30-60 секунд. Телефонные звонки, от которых я не получаю много, были еще хуже. Когда я пропустил звонок, потому что основной пользовательский интерфейс моего телефона не отвечал, я чуть не бросил его в бассейн. Я перепробовал много вещей, но в итоге вытер телефон и восстановился из резервной копии. Это «помогло», но телефон все еще толстый. Я решил, что пришло время переключиться на iOS, и решил, что iPhone 6S + будет отличным телефоном. Я также решил, что новая программа обновления iPhoneбыло бы хорошо подходит. Из того, что я прочитал, это лучше, чем следующая программа ATT. Единственная проблема заключается в том, что вы должны пойти в магазин Apple, чтобы подписаться на программу. Мой ближайший магазин Apple находится в Батон-Руж, примерно в часе езды. Стоит подвезти, но только если я знаю, у меня будет устройство, которое можно забрать.
К счастью, у Apple есть классная форма, которую вы можете использовать, чтобы узнать, доступен ли нужный телефон. Вы выбираете свой штат, свой магазин, свою модель, а затем свой перевозчик:
Как видите, ни один не доступен. (Вздох.) Конечно, вы можете переключиться на SIM-карту (и я проверил, что мой HTC и 6S + используют SIM-карты одного типа). В этой форме меня беспокоило несколько вопросов.
- Прежде всего — вы не можете использовать его до 8 утра. Нет, подожди, перестань смеяться, я серьезно. Это веб-система с «открытыми» часами, похожая на розничный магазин. Там, вероятно, причина данных для этого. Я говорил с представителем Apple на прошлой неделе, и они упомянули, что они получают новые данные инвентаризации в 8. Я хотел бы вообразить, что магазины Apple имеют некоторый изощренный крючок в инвентаре в реальном времени, но это, вероятно, не тот случай. Тем не менее, шокирующе видеть «закрытый» знак на веб-сайте.
- Когда я был в Калифорнии на прошлой неделе, я пытался искать вокруг себя. Каждый раз, когда вы переключаете магазины, форма перестраивается. Поэтому, если я выбрал 6S + и ATT, я теряю эти выборы. Теперь причина этого имеет смысл. Возможно, что в другом магазине нет 6S + или ATT, но это все равно раздражает. Это та проблема, с которой умный интерфейсный код мог бы справиться изящно. В Южном Сан-Франциско вокруг меня было 5-6 магазинов, и я проверял там каждый день, и эти проклятые выпады раздражали меня каждый день. (Как я уже сказал сверху, я, вероятно, не являюсь целевым пользователем здесь.)
- Наконец, было бы очень приятно, если бы я просто сказал: «Скажите мне, когда 6S + для ATT или без SIM-карты доступен в сером или серебристом цвете с 64 ГБ, поскольку 16 просто глупо». Но, очевидно, у Apple нет проблем с продажей iPhone, поэтому такая система, вероятно, не является для них приоритетной. (И чтобы быть ясно, это только для программы обновления. Очевидно, что «обычный» магазин позволяет вам купить прямо сейчас.)
Итак — скучно на этих выходных — я сделал то, что делает любой уважающий себя веб-разработчик — я открыл инструменты разработки, используя форму. Первое, что я заметил, было то, что приложение запускало JSON-файлы для запуска выпадающих меню:
Затем я открыл каждый из этих файлов и посмотрел на JSON. stores.json
был буквальный список всех магазинов с доступностью. Вот фрагмент кода:
"stores" : [ {
"storeNumber" : "R414",
"storeName" : "4th Street",
"storeEnabled" : false,
"storeState" : "California",
"sellEdition" : false,
"storeCity" : "Berkeley"
}, {
"storeNumber" : "R177",
"storeName" : "ABQ Uptown",
"storeEnabled" : true,
"storeState" : "New Mexico",
"sellEdition" : false,
"storeCity" : "Albuquerque"
}, {
availability.json
были данные о доступности конечно. Вот фрагмент из этого:
"R327" : {
"MKVJ2LL/A" : "NONE",
"MKQA2LL/A" : "ALL",
"MKT62LL/A" : "ALL",
"MKQX2LL/A" : "ALL",
"MKR92LL/A" : "ALL",
"MKVV2LL/A" : "NONE",
"MKW72LL/A" : "NONE",
"MKRQ2LL/A" : "ALL",
"MKTM2LL/A" : "NONE",
"MKQ62LL/A" : "ALL",
"MKTA2LL/A" : "ALL",
"MKT72LL/A" : "ALL",
"MKRR2LL/A" : "ALL",
"MKV32LL/A" : "NONE",
"MKVW2LL/A" : "NONE",
"MKW82LL/A" : "NONE",
"MKTN2LL/A" : "NONE",
"MKRE2LL/A" : "ALL",
"MKR82LL/A" : "ALL",
"MKWD2LL/A" : "NONE",
"MKQ72LL/A" : "ALL",
"MKRC2LL/A" : "ALL",
"MKVX2LL/A" : "NONE",
"MKW92LL/A" : "NONE",
"MKVU2LL/A" : "ALL",
"MKW62LL/A" : "NONE",
"MKRF2LL/A" : "ALL",
"MKUQ2LL/A" : "NONE",
"MKV22LL/A" : "NONE",
"MKQY2LL/A" : "ALL",
"MKTY2LL/A" : "NONE",
"MKV52LL/A" : "ALL",
"MKT92LL/A" : "ALL",
"MKT32LL/A" : "ALL",
"MKQ82LL/A" : "ALL",
"timeSlot" : {
"en_US" : {
"timeslotTime" : "11:00 AM",
"contractTimeslotTime" : "11:00 AM"
}
},
Ключом является хранилище, и каждая позиция (кроме timeSlot) представляет позицию модели / цвета / носителя / размера. Так как я смог получить данные (щелкнуть правой кнопкой мыши в инструментах разработчика и открыть их на новой вкладке, а затем сохранить как), я начал работу над веб-приложением, которое позволило бы мне анализировать данные по-своему. В частности, я хотел несколько вещей:
- Позвольте мне указать магазин, а затем несколько магазинов.
- Позвольте мне указать любую модель, которую я хочу.
- Позвольте мне указать несколько перевозчиков.
Я также хотел игнорировать 16 ГБ, но в конце концов решил против этого. Я начал работать над своим собственным кодом, который будет всасывать файлы JSON (моя локальная копия) и позволил мне разобрать его сам. Сначала я покажу результат, а затем расскажу о коде. И да — мой гораздо красивее, чем Apple.
Вверху вы можете увидеть раскрывающееся состояние и селектор магазина. Как я уже сказал, мой первоначальный план состоял в том, чтобы добавить несколько магазинов, но я так и не нашел этого.
Ниже вы можете увидеть выбор перевозчика и модели. Ниже это сетка вариантов. Я использовал CSS (woot) для серых / размытых опций, которые не были доступны. Как я получил цвета Apple iPhone? Знаете ли вы, что Firefox имеет встроенный инструмент выбора цвета?
Круги в магазине Apple на самом деле имеют хорошие градиенты, когда вы перемещаетесь из центра круга наружу. Я просто нажал «в середине», чтобы получить значение, которое выглядело хорошо для меня.
Хорошо, теперь давайте перейдем к коду кода. Я начал с простой процедуры настройки:
$(document).ready(function() {
console.log("Make it so.");
//load json files
var storeReq = $.getJSON("data/stores.json");
var availReq = $.getJSON("data/availability.json");
$.when(storeReq,availReq).then(function(stores, avail) {
storeList = stores[0].stores;
availabilityData = avail[0];
doStoresForStates();
doStateDropDowns();
startUp();
});
});
Я пропущу doStoresForStates
, это просто дает мне корневую переменную с ключом состояния, которая включает в себя массив хранилищ. doStateDropDowns заполняет раскрывающееся состояние. Как я уже говорил, идея состояла в том, чтобы сделать так, чтобы вы могли добавить несколько магазинов, но я так и не дошел до этого.
function doStateDropDowns() {
var dds = $(".stateDD");
//generate the option HTML list, but only once
if(stateOptionHTML === "") {
var states = [];
for(var i=0;i<storeList.length;i++) {
if(states.indexOf(storeList[i].storeState) === -1) states.push(storeList[i].storeState);
}
states.sort();
var s = "
-- State --"; for(var i=0;i<states.length;i++) { s += "
" + states[i] + ""; } stateOptionHTML = s; } dds.each(function(index) { console.log("doing "+index); var options = $("option", this); if(options.length === 0) { $(this).html(stateOptionHTML); } }); }
В раскрывающемся списке состояния есть прослушиватель событий, который реагирует на изменения. Единственное, что здесь хорошо, — это использование next("select")
рядом с ним.
function doStores() {
var selected = $(this).val();
if(selected === "") return;
var storeHTML = "
-- Location --"; for(var i=0;i " + storeData[selected][i].city + ", " + storeData[selected][i].name + ""; } $(this).next("select").html(storeHTML); }
Итак, теперь наступил страшный момент — создание фактического материала «на основе того, что вы выбираете, фильтруйте результаты». Первой проблемой, с которой я столкнулся, были данные модели. Как я уже сказал, каждая модель / емкость / цвет / носитель имеет уникальный идентификатор. Я мог бы набрать все это вручную, но вместо этого я использовал инструменты dev:
var ray = [];$(".form-choice-selector").each(function(idx) { ray.push($(this).val()); }); copy(JSON.stringify(ray));
То, что вы видите, это код, который я запустил в консоли браузера. Он извлекал каждую «ячейку» дисплея, получал значение (которое было идентификатором продукта), а затем использовал его copy
для помещения в мою буфер обмена. Затем я могу вставить в мой код. Есть 5 носителей и 2 модели, поэтому мне пришлось делать это 10 раз, но это заняло всего 1 или 2 минуты, так что это не имело большого значения.
Фильтрующий код — большой беспорядок. Мол, серьезно. Кажется, работает, но я не даю на это никаких гарантий. Вот оно — не смейся надо мной слишком сильно.
function doFilter() {
//get all locations
var locations = [];
var selectedModels = [];
var selectedCarriers = [];
$(".locationDD").each(function(idx) {
if($(this).val() != '') locations.push($(this).val());
});
//if no locations, do nothing
if(locations.length === 0) return;
$(".modelCB").each(function(idx) {
if($(this).is(':checked')) {
selectedModels.push($(this).val());
};
});
$(".carrierCB").each(function(idx) {
if($(this).is(':checked')) {
selectedCarriers.push($(this).val());
};
});
console.log("Begin to filter. "+JSON.stringify(locations)+" "+JSON.stringify(selectedModels)+" "+JSON.stringify(selectedCarriers));
/*
logic is: for each color/capacity, determine if ON/OFF
*/
for(var i=0;i<capacityData.length;i++) {
var capacity = capacityData[i];
for(var j=0;j<colorData.length;j++) {
var color = colorData[j];
var models = getModels(capacity, color, selectedCarriers, selectedModels);
console.log("check "+capacity+" "+color+" models="+JSON.stringify(models));
var enabled = false;
//did we filter by location?
if(locations.length > 0) {
for(var z = 0;z<locations.length;z++) {
var location = locations[z];
for(var k=0;k<models.length;k++) {
//console.log(availabilityData[location][models[k]]);
if(availabilityData[location][models[k]] === "ALL") {
enabled=true;
break;
}
}
}
}
console.log("ENABLED",enabled);
var cell = $("." + color + ".cap" + capacity);
if(!enabled) {
cell.addClass("outofstock");
} else {
cell.removeClass("outofstock");
}
}
}
}
По сути — я зацикливаюсь на массиве емкости и цвета, а затем проверяю доступность в каждом месте. (Опять же, помните, что я собирался поддерживать несколько местоположений.) getModels
— это служебная функция, которая анализирует данные модели, которые я почерпнул из инструментов разработчика. Затем я просто добавляю / удаляю класс CSS, чтобы добавить приятный эффект серого / размытия.
Это был передний конец. Чтобы поддерживать приложение в актуальном состоянии, я поместил его в приложение Node.js, работающее в IBM Bluemix . Все, что мне было нужно, — это возможность отсасывать JSON-файлы из Apple по расписанию, и для этого я использовал библиотеку cron, которую использовал в ColdFusion Bloggers . Вот полное приложение:
/*eslint-env node*/
var https = require('https');
var fs = require('fs');
// This application uses express as its web server
// for more info, see: http://expressjs.com
var express = require('express');
// cfenv provides access to your Cloud Foundry environment
// for more info, see: https://www.npmjs.com/package/cfenv
var cfenv = require('cfenv');
// create a new express server
var app = express();
// serve the files out of ./public as our main files
app.use(express.static(__dirname + '/public'));
// get the app environment from Cloud Foundry
var appEnv = cfenv.getAppEnv();
//fire and forget sync method
var sync = function() {
var writeStream1 = fs.createWriteStream('./public/data/availability.json');
https.request('https://reserve.cdn-apple.com/US/en_US/reserve/iPhone/availability.json', function(res) {
res.pipe(writeStream1);
}).end();
var writeStream2 = fs.createWriteStream('./public/data/stores.json');
https.request('https://reserve.cdn-apple.com/US/en_US/reserve/iPhone/stores.json', function(res) {
res.pipe(writeStream2);
}).end();
}
var cron = require('cron');
var cronJob = cron.job('* */2 * * *', function() {
sync();
console.log('cron job complete');
});
cronJob.start();
// start server on the specified port and binding host
app.listen(appEnv.port, function() {
// print a message when the server starts listening
console.log("server starting on " + appEnv.url);
});
У меня нет какой-либо обработки ошибок в функции синхронизации, так что она хрупкая, но справляется с работой. Я также настроил это, чтобы поразить Apple раз в 2 часа. Я подумал, что это было нежно и не будет завышать налог Apple.com Я также создал маршрут, который мог бы использовать для ручного тестирования, но удалил его, когда развернул его до Bluemix.
Вы можете увидеть это сами здесь: http://applestorechecker.mybluemix.net/ . Как я уже сказал, это несколько хрупко. Я мог бы также добавить простые «файлы данных, последние обновленные в X» в заголовок, чтобы вы знали, насколько свежи данные. Если бы я не был ленивым, я мог бы также добавить возможность для вас зарегистрироваться, когда у вашей желаемой модели / цвета / носителя / магазина есть продукт, но, увы, я ленивый сегодня.
В любом случае, проверьте это, и дайте мне знать, если у вас есть какие-либо вопросы!