javascript設計モード-プロキシモード
31250 ワード
<!DOCTYPE html>
<html>
<head>
<title> </title>
<meta charset="utf-8">
</head>
<body>
<script>
/**
*
*
* :
* 。
*
* :
*
*
* (proxy) , 。 , 。 (real subject)。 , 。 , , 。 。 , 。
*/
/*
。 ( ) 。 。 。 。 , ( ), ( )。 , 。
*/
(function(){
//
// ,
function Subject(){}
Subject.prototype.request = function(){};
/**
*
* @param {Object} realSubject [ ]
*/
function Proxy(realSubject){
this.realSubject = readSubject;
}
Proxy.prototype.request = function(){
this.realSubject.request();
};
}());
/*
。 。 , 。
, 。 Book
*/
var Publication = new Interface('Publication', ['getIsbn', 'setIsbn', 'getTitle', 'setTitle', 'getAuthor', 'display']);
var Book = function (isbn, title, author) {
// implements Publication
};
// Library interface
var Library = new Interface('Library', ['findBooks', 'checkoutBook', 'returnBook']);
// PublicLibrary class
var PublicLibrary = function (books) {
// implements Library
this.catalog = {};
for (var i = 0, len = books.length; i < len; i++) {
this.catalog[books[i].getIsbn()] = {
book: books[i],
available: true
};
}
};
PublicLibrary.prototype = {
findBooks: function (searchString) {
var results = [];
for (var isbn in this.catalog) {
if (!this.catalog.hasOwnProperty(isbn)) {
continue;
}
if (searchString.match(this.catalog[isbn].getTitle()) || searchString.match(this.catalog[isbn].getAnchor())) {
results.push(this.catalog[isbn]);
}
}
return results;
},
checkoutBook: function (book) {
var isbn = book.getIsbn();
if (this.catalog[isbn]) {
if (this.catalog[isbn].available) {
this.catalog[isbn].available = false;
return this.catalog[isbn];
} else {
throw new Error('PublicLibrary:book ' + book.getTitle() + ' is not currently available.');
}
} else {
throw new Error('PublicLibrary:book ' + book.getTitle() + ' not found');
}
},
returnBook: function (book) {
var isbn = book.getIsbn();
if (this.catalog[isbn]) {
this.catalog[isbn].available = true;
} else {
throw new Error('PublicLibrary:book ' + book.getTitle() + ' nout found');
}
}
};
/*
。 , 。 PublicLibrary :
*/
// PublicLibraryProxy class, a useless proxy
var PublicLibraryProxy = function (catalog) {
// implements Library
this.library = new PublicLibrary(catalog);
};
PublicLibraryProxy.prototype = {
findBooks: function (searchString) {
return this.library.findBooks(searchString);
},
checkoutBook: function (book) {
return this.library.checkoutBook(book);
},
returnBook: function (book) {
return this.library.returnBook(book);
}
};
/*
。 , (virtual proxy) 。 。 。 。 。 , PublicLibrary 。 。 , PublicLibrary 。
*/
// PublicLibarryVirtualProxy class
var PublicLibraryVirtualProxy = function (catalog) {
this.library = null;
this.catalog = catalog;
};
PublicLibraryVirtualProxy.prototype = {
_initializeLibrary: function () {
if (this.library === null) {
this.library = new PublicLibrary(this.catalog);
}
},
findBooksL: function (searchString) {
this._initializeLibrary();
return this.library.findBooks(searchString);
},
checkoutBook: function (book) {
this._initializeLibrary();
return this.library.checkoutBook(book);
},
returnBook: function (book) {
this._initializeLibrary();
return this.library.returnBook(book);
}
};
/*
PublicLibraryProxy PublicLibraryVirtualProxy PublicLibrary 。PublicLibraryVirtualProxy , 。 , , 。 。 , 。
*/
/*
,
JS , 。
(remote proxy) 。 Java , , 。 , 。 JS 。 JS 。 JS , 。 JSON , Ajax 。
。 Web , PHP 。 , 。 , 。
JS 。 , 。 PublicLibrary 。 Java , ( ) , 。 JS , 。
*/
/*
。 , , 。
, 。 , 。 。 。 , 。 , 。 , 。 , , 。 , , 。
*/
/*
, 。 。 。 , , 。 “ 。。。” , , 。
。 , , XMLHttpRequest 。 ? 。 , 。 , 。 ( ) , 。 , JS API。
, , , 。 , , 。
*/
var SimpleHandler = function () {
}; // implements AjaxHandler
SimpleHandler.prototype = {
request: function (method, url, callback, postVars) {
var xhr = this.createXhrObject();
xhr.onreadystatechange = function () {
if (xhr.readyState !== 4) {
return;
}
((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) ?
callback.success(xhr.responseText, xhr.responseXML) :
callback.failure(xhr.status);
};
xhr.open(method, url, true);
if (method !== 'POST') {
postVars = null;
}
xhr.send(postVars);
},
createXhrObject: function () { // Factory method
var methods = [
function () {
return new XMLHttpRequest();
},
function () {
return new ActiveXObject('Msxml2.XMLHTTP');
},
function () {
return new ActiveXObject('Microsoft.XMLHTTP');
}
];
for (var i = 0, len = methods.length; i < len; i++) {
try {
methods[i]();
} catch (e) {
continue;
}
// if we reach this point,method[i] worked
this.createXhrObject = methods[i](); // Memoize the method
return methods[i]();
}
// if we reach this point,none of the methods worked
throw new Error('SimpleHandler:Could not create an XHR object.');
}
};
// Manually making the calls
var xhrHandler = new SimpleHandler();
// Get he pageview statistics
var callback = {
success: function (responseText) {
// Parse the JSON data
var stats = eval('(' + responseText + ')');
// Display the stats on the page
displayPageviews(stats);
},
failure: function (statusCode) {
throw new Error('Asynchronous request for stats failed.');
}
};
xhrHandler.request('GET', '/stats/getPageviews/?page=index.html', callback);
// Get the browser statistics
var callback2 = {
success: function (responseText) {
var stats = eval('(' + responseText + ')');
displayBrowserShare('Asynchronous request for stats failed');
}
};
xhrHandler.request('GET', '/stats/getBrowserShare/?page=index.html', callback);
// :
// PgeStats interface
var PageStats = new Interface('PageStats', ['getPageviews', 'getUniques', 'getBrowserShare', 'getTopSearchTerms', 'getMostVisitedPages']);
// StatsProxy:
// StatsProxy singleton
var StatsProxy = (function () {
// Private attributes
var xhrHandler = new SimpleHandler();
var urls = {
pageviews: '/stats/getPageviews/',
uniques: '/stats/getUniques/',
browserShare: '/stats/getBrowserShare/',
topSearchTerms: '/stats/getTopSearchTerms/',
mostVisitedPages: '/stats/getMostVisitedPages/'
};
// Private methods
function xhrFailure() {
throw new Error('StatsProxy: Asynchronous request for stats failed.');
}
function fetchData(url, dataCallback, startDate, endDate, page) {
var callback = {
success: function (responseText) {
var stats = eval('(' + responseText + ')');
dataCallback(stats);
},
failure: xhrFailure
};
var getVars = [];
if (startDate !== undefined) {
getVars.push('startDate=' + encodeURIComponent(startDate));
}
if (endDate !== undefined) {
getVars.push('endDate=' + encodeURIComponent(endDate));
}
if (page !== undefined) {
getVars.push('page=' + page);
}
if (getVars.length > 0) {
url = url + '?' + getVars.join('&');
}
xhrHandler.request('GET', url, callback);
}
// Public methods
return {
getPageviews: function (callback, startDate, endDate, page) {
fetchData(urls.pageviews, callback, startDate, endDate, page);
},
getUniques: function (callback, startDate, endDate, page) {
fetchData(urls.uniques, callback, startDate, endDate, page);
},
getBrowserShare: function (callback, startDate, endDate, page) {
fetchData(urls.browserShare, callback, startDate, endDate, page);
},
getTopSearchTerms: function (callback, startDate, endDate, page) {
fetchData(urls.topSearchTerms, callback, startDate, endDate, page);
},
getMostVisitedPages: function (callback, startDate, endDate, page) {
fetchData(urls.mostVisitedPages, callback, startDate, endDate, page);
}
};
}());
/*
Web , 。 StatsProxy JS , 。 , 。 , , , , , , , 。 , 。 , 。 , 。
*/
/*
Web
Web 。 JS ,Web 。 , 。
*/
// WebserviceProxy class
var WebserviceProxy = function () {
this.xhrHandler = new SimpleHandler();
};
WebserviceProxy.prototype = {
_xhrFailure: function (statusCode) {
throw new Error('StatsProxy:Asynchronous request for stats failed.');
},
_fetchData: function (url, dataCallback, getVars) {
var that = this;
var callback = {
success: function (responseText) {
var obj = eval('(' + responseText + ')');
dataCallback(obj);
},
failure: that._xhrFailure
};
var getVarArray = [];
for (var varName in getVars) {
getVarArray.push(varName + '=' + getVars[varName]);
}
if (getVarArray.length > 0) {
url = url + '?' + getVarArray.join('&');
}
this.xhrHandler.request('GET', url, callback);
}
};
/*
, WebserviceProxy , _fetchData :
*/
// StatsProxy class
// StatsProxy class
var StatsProxy = function () {
StatsProxy.superclass.constructor.call(this);
};
function extend(subClass, superClass) {
var F = function () {
};
F.prototype = superClass.prototype;
subClass.prototype = new F();
subClass.prototype.constructor = subClass;
subClass.superclass = superClass.prototype;
if (superClass.prototype.constructor === Object.prototype.constructor) {
superClass.prototype.constructor = superClass;
}
}
extend(StatsProxy, WebserviceProxy);
StatsProxy.prototype.getPageviews = function (callback, startDate, endDate, page) {
this._fetchData('test.json', callback, {
'startDate': startDate,
'endDate': endDate,
'page': page
});
};
StatsProxy.prototype.getUniques = function (callback, startDate, endDate, page) {
this._fetchData('/stats/getsUniques/', callback, {
'startDate': startDate,
'endDate': endDate,
'page': page
});
};
/*
。 。 A , 。 , 。
, 。 , 。 , 。 , 。
*/
/*
。 HTML , :
*/
// Directory interface
var Directory = new Interface('Directory', ['showPage']);
// PersonnelDirectory class, the Real Subject
var PersonnelDirectory = function (parent) {
this.xhrHandler = new SimpleHandler();
this.parent = parent;
this.data = null;
this.currentPage = null;
var that = this;
var callback = {
success: that._configure,
failure: function () {
throw new Error('PersonnelDirectory: failure in data retrieval.');
}
};
xhrHandler.request('GET', 'directoryData.php', callback);
};
PersonnelDirectory.prototype = {
_configure: function (responseText) {
this.data = eval('(' + responseText + ')');
//...
this.currentPage = 'a';
},
showPage: function (page) {
$('page-' + this.currentPage).style.display = 'none';
$('page-' + page).style.display = 'block';
this.currentPage = page;
}
};
/*
XHR 。 ——configure , HTML 。 。 、 。
*/
// , 。
// showPage
// DirectoryProxy class, just the outline
var DirectoryProxy = function (parent) {
};
DirectoryProxy.prototype = {
showPage: function (page) {
}
};
// ,
// :
// DirectoryProxy class, as a useless proxy
var DirectoryProxy = function (parent) {
this.directory = new PersonnelDirectory(parent);
};
DirectoryProxy.prototype = {
showPage: function (page) {
return this.directory.showPage(page);
}
};
/*
PersonnelDirectory 。 。 , 。 , ,
*/
// DirectoryProxy class, as a virtual proxy
var DirectoryProxy = function (parent) {
this.parent = parent;
this.directory = null;
var that = this;
addEvent(parent, 'mouseover', function () {
that._initialize();
});
};
DirectoryProxy.prototype = {
_initialize: function () {
this.directory = new PersonnelDirectory(this.parent);
},
showPage: function (page) {
return this.directory.showPage(page);
}
};
/*
, , 。 , , , 。
。 , , :
*/
// DirectoryProxy class, with loading message
var DirectoryProxy = function (parent) {
this.parent = parent;
this.directory = null;
this.warning = null;
this.interval = null;
this.initialized = false;
var that = this;
addEvent(parent, 'mouseover', function () {
that._initialize();
});
};
DirectoryProxy.prototype = {
_initialize: function () {
this.warning = document.createElement('div');
this.parent.appendChild(this.warning);
this.warning.innerHTML = 'The company directory is loading...';
this.directory = new PersonnelDirectory(this.parent);
var that = this;
this.interval = setInterval(function () {
that._checkInitialization();
}, 100);
},
_checkInitialization: function () {
if (this.directory.currentPage !== null) {
clearInterval(this.interval);
this.initialized = true;
this.parent.removeChild(this.warning);
}
},
showPage: function (page) {
if (!this.initialized) {
return;
}
return this.directory.showPage(page);
}
};
/*
showPage , initialized , true 。 , 。 ? , , 。 PersonnelDirectory currentPage , 100 , 。 。
*/
/**
*
*
* JS , , , , 。 , _initialize _checkInitialization 。 , 。
*/
// DynamicProxy abstract class, incomplete
var DynamicProxy = function () {
this.args = arguments;
this.initialized = false;
};
DynamicProxy.prototype = {
_initialize: function () {
// Instantiate the class
/*
this.subject = {};
this.class1.apply(this.subject, this.args);
this.subject.__proto__ = this.class1.prototype;
*/
this.subject = new this.class1(this.args);
var that = this;
this.interval = setInterval(function () {
that._checkInitialization();
}, 100);
},
_checkInitialization: function () {
if (this._isInitialized()) {
clearInterval(this.interval);
this.initialized = true;
}
},
_isInitialized: function () {
throw new Error('Unsupported operation on an abstract class.');
}
};
//
// DynamicProxy abstract class, complete
var DynamicProxy = function () {
this.args = arguments;
this.initialized = false;
if (typeof this.class1 !== 'function') {
throw new Error('DynamicProxy: tha class attribute must be set before calling the super-class constructor.');
}
// Create the methods needed to implement the same interface.
for (var key in this.class1.prototype) {
// Ensure that the property is a function
if (this.class1.prototype.hasOwnProperty(key)) {
if (typeof this.class1.prototype[key] !== 'function') {
continue;
}
// Add the method
var that = this;
(function (methodName) {
that[methodName] = function () {
if (!that.initialized) {
return;
}
return that.subject[methodName].apply(that.subject, arguments);
};
}(key));
}
}
};
DynamicProxy.prototype = {
_initialize: function () {
// Instantiate the class
/*
this.subject = {};
this.class1.apply(this.subject, this.args);
this.subject.__proto__ = this.class1.prototype;
*/
this.subject = new this.class1(this.args);
var that = this;
this.interval = setInterval(function () {
that._checkInitialization();
}, 100);
},
_checkInitialization: function () {
if (this._isInitialized()) {
clearInterval(this.interval);
this.initialized = true;
}
},
_isInitialized: function () {
throw new Error('Unsupported operation on an abstract class.');
}
};
/*
, prototype , 。 , , 。 , , , 。
, :
*/
// TestProxy class
var TestClass = function () {
console.log('start');
};
TestClass.prototype = {
f1: function () {
console.log(1);
},
f2: function () {
console.log(2);
}
};
var TestProxy = function () {
this.class1 = TestClass;
var that = this;
addEvent($('test-link'), 'click', function () {
that._initialize();
that.f1();
});
TestProxy.superclass.constructor.apply(this, arguments);
};
extend(TestProxy, DynamicProxy);
TestProxy.prototype._isInitialized = function () {
//...
// Initialization condition goes here
if (!this.initialized) {
for (var i = 0, s = ''; i < 10; i++) {
s += '<li>' + i + '</li>';
}
document.getElementById('content').innerHTML += '<ul>' + s + '</ul>';
return true;
}
return false;
};
var p = new TestProxy();
/*
4 ; this.class ; ( ); ; _isInitialized ( true false)
*/
/**
*
*
* 。 , 。 , , 。
*
*
*
* , 。 。 , 。 ,
*/
/* Title: Proxy
Description: provides a placeholder for another object to control access, reduce cost, and reduce complexity
*/
var $ = function (id) {
return document.getElementById(id);
};
var http = {
makeRequest:function (ids, callback) {
var url = 'http://query.yahooapis.com/v1/public/yql?q=',
sql = 'select * from music.video.id where ids IN ("%ID%")',
format = "format=json",
handler = "callback=" + callback,
script = document.createElement('script');
sql = sql.replace('%ID%', ids.join('","'));
sql = encodeURIComponent(sql);
url += sql + '&' + format + '&' + handler;
script.src = url;
document.body.appendChild(script);
}
};
var proxy = {
ids:[],
delay:50,
timeout:null,
callback:null,
context:null,
makeRequest:function (id, callback, context) {
// add to the queue
this.ids.push(id);
this.callback = callback;
this.context = context;
// set up timeout
if (!this.timeout) {
this.timeout = setTimeout(function () {
proxy.flush();
}, this.delay);
}
},
flush:function () {
http.makeRequest(this.ids, 'proxy.handler');
// clear timeout and queue
this.timeout = null;
this.ids = [];
},
handler:function (data) {
var i, max;
// single video
if (parseInt(data.query.count, 10) === 1) {
proxy.callback.call(proxy.context, data.query.results.Video);
return;
}
// multiple videos
for (i = 0, max = data.query.results.Video.length; i < max; i += 1) {
proxy.callback.call(proxy.context, data.query.results.Video[i]);
}
}
};
var videos = {
getPlayer:function (id) {
return '' +
'<object width="400" height="255" id="uvp_fop" allowFullScreen="true">' +
'<param name="movie" value="http://d.yimg.com/m/up/fop/embedflv/swf/fop.swf"\/>' +
'<param name="flashVars" value="id=v' + id + '&eID=1301797&lang=us&enableFullScreen=0&shareEnable=1"\/>' +
'<param name="wmode" value="transparent"\/>' +
'<embed ' +
'height="255" ' +
'width="400" ' +
'id="uvp_fop" ' +
'allowFullScreen="true" ' +
'src="http://d.yimg.com/m/up/fop/embedflv/swf/fop.swf" ' +
'type="application/x-shockwave-flash" ' +
'flashvars="id=v' + id + '&eID=1301797&lang=us&ympsc=4195329&enableFullScreen=1&shareEnable=1"' +
'\/>' +
'<\/object>';
},
updateList:function (data) {
var id,
html = '',
info;
if (data.query) {
data = data.query.results.Video;
}
id = data.id;
html += '<img src="' + data.Image[0].url + '" width="50" \/>';
html += '<h2>' + data.title + '<\/h2>';
html += '<p>' + data.copyrightYear + ', ' + data.label + '<\/p>';
if (data.Album) {
html += '<p>Album: ' + data.Album.Release.title + ', ' + data.Album.Release.releaseYear + '<br \/>';
}
html += '<p><a class="play" href="http://new.music.yahoo.com/videos/--' + id + '">» play<\/a><\/p>';
info = document.createElement('div');
info.id = "info" + id;
info.innerHTML = html;
$('v' + id).appendChild(info);
},
getInfo:function (id) {
var info = $('info' + id);
if (!info) {
proxy.makeRequest(id, videos.updateList, videos);
return;
}
if (info.style.display === "none") {
info.style.display = '';
} else {
info.style.display = 'none';
}
}
};
$('vids').onclick = function (e) {
var src, id;
e = e || window.event;
src = e.target || e.srcElement;
if (src.nodeName.toUpperCase() !== "A") {
return;
}
if (typeof e.preventDefault === "function") {
e.preventDefault();
}
e.returnValue = false;
id = src.href.split('--')[1];
if (src.className === "play") {
src.parentNode.innerHTML = videos.getPlayer(id);
return;
}
src.parentNode.id = "v" + id;
videos.getInfo(id);
};
$('toggle-all').onclick = function (e) {
var hrefs,
i,
max,
id;
hrefs = $('vids').getElementsByTagName('a');
for (i = 0, max = hrefs.length; i < max; i += 1) {
// skip play links
if (hrefs[i].className === "play") {
continue;
}
// skip unchecked
if (!hrefs[i].parentNode.firstChild.checked) {
continue;
}
id = hrefs[i].href.split('--')[1];
hrefs[i].parentNode.id = "v" + id;
videos.getInfo(id);
}
};
// http://www.dofactory.com/javascript-proxy-pattern.aspx
function GeoCoder() {
this.getLatLng = function(address) {
if (address === "Amsterdam") {
return "52.3700° N, 4.8900° E";
} else if (address === "London") {
return "51.5171° N, 0.1062° W";
} else if (address === "Paris") {
return "48.8742° N, 2.3470° E";
} else if (address === "Berlin") {
return "52.5233° N, 13.4127° E";
} else {
return "";
}
};
}
function GeoProxy() {
var geocoder = new GeoCoder();
var geocache = {};
return {
getLatLng: function(address) {
if (!geocache[address]) {
geocache[address] = geocoder.getLatLng(address);
}
log.add(address + ": " + geocache[address]);
return geocache[address];
},
getCount: function() {
var count = 0;
for (var code in geocache) { count++; }
return count;
}
};
};
// log helper
var log = (function() {
var log = "";
return {
add: function(msg) { log += msg + "
"; },
show: function() { alert(log); log = ""; }
}
})();
function run() {
var geo = new GeoProxy();
// geolocation requests
geo.getLatLng("Paris");
geo.getLatLng("London");
geo.getLatLng("London");
geo.getLatLng("London");
geo.getLatLng("London");
geo.getLatLng("Amsterdam");
geo.getLatLng("Amsterdam");
geo.getLatLng("Amsterdam");
geo.getLatLng("Amsterdam");
geo.getLatLng("London");
geo.getLatLng("London");
log.add("
Cache size: " + geo.getCount());
log.show();
}
</script>
</body>
</html>