3 changed files with 509 additions and 0 deletions
@ -0,0 +1,411 @@ |
|||
/* |
|||
--- |
|||
description: Templated is a MooTools mixin class which creates elements for classes using a string-based HTML template which may included embedded attach points and events. |
|||
|
|||
license: MIT-style |
|||
|
|||
authors: |
|||
- David Walsh |
|||
|
|||
requires: |
|||
- core/1.3: '*' |
|||
|
|||
provides: [Templated] |
|||
|
|||
... |
|||
*/ |
|||
|
|||
// Create scope limiter
|
|||
(function(scope) { |
|||
|
|||
// Create some vars for strings
|
|||
// This will save bytes when compressed
|
|||
var strData = "data", |
|||
strWidget = "widget", |
|||
strAttach = "attach", |
|||
dataWidgetType = strData + "-" + strWidget + "-type", |
|||
dataWidgetProps = strData + "-" + strWidget + "-props", |
|||
dataWidgetAttachPoint = strData + "-" + strWidget + "-" + strAttach + "-point", |
|||
dataWidgetAttachEvent = strData + "-" + strWidget + "-" + strAttach + "-event", |
|||
dataWidgetized = strData + "-widgetized"; |
|||
|
|||
// Templated is a mixin for UI widgets
|
|||
scope.Templated = new Class({ |
|||
|
|||
// The usual options object
|
|||
options: { |
|||
// The default template
|
|||
template: "<div></div>", |
|||
|
|||
// The URL to get the template from *instead* of the string
|
|||
templateUrl: "", |
|||
|
|||
// A node reference for where this UI widget will be placed...in reference to
|
|||
element: null, |
|||
|
|||
// Should this widget be parsed for sub-widgets?
|
|||
widgetsInTemplate: true, |
|||
|
|||
// Property mappings (should be an object)
|
|||
// These override defaults
|
|||
propertyMappings: null, |
|||
|
|||
// Default property mappings
|
|||
// Thse properties on the element will be moved to the respective nodes within the template
|
|||
defaultPropertyMappings: null, |
|||
|
|||
// Should messages be debug to the console
|
|||
debugMode: true |
|||
}, |
|||
|
|||
// Create placeholders for attached points and events
|
|||
_attachPoints: [], |
|||
_attachEvents: [], |
|||
|
|||
// Parse
|
|||
parse: function() { |
|||
|
|||
// Get shortcuts to the options and element
|
|||
var options = this.options, |
|||
nodeRef = options.element = options.element || new Element("div").inject(document.body); |
|||
|
|||
// *IF* a templateUrl is specified, can't do anything until template is loaded
|
|||
// Defer parsing until we've got it
|
|||
if(options.templateUrl && Templated.templates && !Templated.templates[options.templateUrl]) { |
|||
this.debug("[Templated:parse] Need to load template from URL: " + options.templateUrl); |
|||
this.getTemplate(); |
|||
return false; |
|||
} |
|||
|
|||
// If already data-widgetized...gtfo
|
|||
if(nodeRef.retrieve("widget")) { |
|||
this.debug("[Templated:parse] Node already widgetized, leaving ", nodeRef); |
|||
return nodeRef.domNode; |
|||
} |
|||
|
|||
// Mix noderef properties with options
|
|||
options.defaultPropertyMappings = options.defaultPropertyMappings || { // THESE OVERRIDE CLASSES IN THE TEMPLATE!!!!
|
|||
"id": "domNode", |
|||
"style": "domNode", |
|||
"class": "domNode" |
|||
}; |
|||
Object.merge(options, this.getNodeProps(nodeRef)); |
|||
|
|||
// postMixInProperties runs after options have been mixed with defaults but before
|
|||
// any templating is done
|
|||
this.postMixInProperties(); |
|||
|
|||
// Build rendering - creates the actual nodes, attachpoints, and attachevents
|
|||
this.buildRendering(); |
|||
|
|||
// Fire the "postCreate" method, which runs after nodes are created *but* before the nodes are rendered to the page
|
|||
this.postCreate(); |
|||
|
|||
// Cleanup creation
|
|||
this.cleanupCreation(); |
|||
|
|||
// "Startup": The widget is in the DOM and the widget is ready to go
|
|||
this.startup(); |
|||
|
|||
// Return the domNode
|
|||
this.debug("[Templated:parse] At the end of parse, this is: ", this); |
|||
return this.domNode; |
|||
}, |
|||
|
|||
// Creates build rendering
|
|||
buildRendering: function() { |
|||
// Get shortcuts to the options and element
|
|||
var options = this.options, nodeRef = options.element; |
|||
|
|||
// Do string substitution on the template
|
|||
var template = this.template = options.template.substitute(options || {}); |
|||
|
|||
// Create the DOM node within a DIV that's not rendered to the page
|
|||
var bitchNode = this.bitchNode = new Element("div", { html: template.trim() }), |
|||
domNode = this.domNode = document.id(bitchNode.childNodes[0]); |
|||
|
|||
// Look for subwidgets if told to...
|
|||
if(options.widgetsInTemplate) { |
|||
this.debug("[Templated:parse] Looking for subwidgets under domNode", domNode); |
|||
this.makeSubWidgets(this.domNode); |
|||
} |
|||
|
|||
// Create the attachpoints for me, then my kiddies
|
|||
this.makeAttachPoints(domNode); |
|||
if(options.widgetsInTemplate) domNode.getElements("[" + dataWidgetAttachPoint + "]").each(this.makeAttachPoints, this); |
|||
this.debug("[Templated:parse] Creating attachpoints", this._attachPoints); |
|||
|
|||
// Create the attachevents for me, then my kiddies
|
|||
this.makeAttachEvents(domNode); |
|||
if(options.widgetsInTemplate) domNode.getElements("[" + dataWidgetAttachEvent + "]").each(this.makeAttachEvents, this); |
|||
this.debug("Creating attachevents", this._attachEvents); |
|||
|
|||
// Map properties to nodes within the template
|
|||
// Mix the custom mappings with the default
|
|||
// This needs to happen after attachpoints
|
|||
var mappings = options.propertyMappings ? Object.merge(options.defaultPropertyMappings, options.propertyMappings) : options.defaultPropertyMappings; |
|||
Object.each(mappings, function(value, key) { |
|||
// Ignore the value if not present in the object
|
|||
if(!this[value]) return; |
|||
// Assign the value to the key
|
|||
var currentProp = nodeRef.get(key); |
|||
if(currentProp != "") this[value].set(key, currentProp); |
|||
}.bind(this)); |
|||
|
|||
// If this widget has a "containerNode", grab it's childNodes *or* inject innerHTML
|
|||
if(this.containerNode) { |
|||
var kids = nodeRef.childNodes; |
|||
kids.length ? $$(kids).inject(this.containerNode) : this.containerNode.set("html", nodeRef.get("html")); |
|||
} |
|||
}, |
|||
|
|||
// "postMixInProperties" -- Fired after options have been mixed in
|
|||
postMixInProperties: function() { |
|||
this.debug("[Templated:postMixInProperties] postMixInProperties!"); |
|||
}, |
|||
|
|||
// "PostCreate" -- Fired after nodes are created, attachpoints and events are found
|
|||
postCreate: function() { |
|||
this.debug("[Templated:postCreate] postCreate!"); |
|||
}, |
|||
|
|||
// "CleanupCreation" -- Removes the old element, destroys bitch node
|
|||
cleanupCreation: function() { |
|||
// Get hold of the dom node and bitch nodes
|
|||
var domNode = this.domNode, bitchNode = this.bitchNode, nodeRef = this.options.element; |
|||
|
|||
// Put the domNode where it should go and destroy the node reference
|
|||
domNode.replaces(nodeRef); |
|||
nodeRef.destroy(); |
|||
|
|||
// Mark as data-widgetized and store the widget within data
|
|||
domNode.set(dataWidgetized, true); |
|||
domNode.store("widget", this); |
|||
|
|||
// Remove the bitch node
|
|||
bitchNode.destroy(); |
|||
}, |
|||
|
|||
// "StartUp" -- Fired when node is in place
|
|||
startup: function(){ |
|||
this.debug("[Templated:startup] startup!"); |
|||
}, |
|||
|
|||
// Focus on focus node, if present
|
|||
focus: function() { |
|||
var node = this.focusNode; |
|||
node && node.focus(); |
|||
}, |
|||
|
|||
// Create subwidgets from this
|
|||
makeSubWidgets: function(domNode) { |
|||
if(!domNode) domNode = this.domNode; |
|||
domNode.getElements("["+ dataWidgetType +"]:not([" + dataWidgetized + "])").each(function(node){ |
|||
// Store the subwidget's attachpoints, attachevents, class type, and properties
|
|||
var points = node.get(dataWidgetAttachPoint), |
|||
events = node.get(dataWidgetAttachEvent), |
|||
widgetProps = this.getNodeProps(node), |
|||
klass = node.get(dataWidgetType).trim(); |
|||
|
|||
// Create the widget
|
|||
if(scope[klass]) { |
|||
var widget = new scope[klass](Object.merge(widgetProps, { element: node })); |
|||
this.debug("[parse:makeSubWidgets] Creating child widget: ", klass, widget); |
|||
// Get access to its dom node
|
|||
widgetDomNode = widget.domNode; |
|||
// Add attachments back to the widget
|
|||
points && widgetDomNode.set(dataWidgetAttachPoint, points.trim()); |
|||
events && widgetDomNode.set(dataWidgetAttachEvent, events.trim()); |
|||
} |
|||
}, this); |
|||
}, |
|||
|
|||
// Makes attachpoints
|
|||
makeAttachPoints: function(node) { |
|||
var points = node.get(dataWidgetAttachPoint); |
|||
if(points) { |
|||
points.trim().split(",").each(function(attach) { |
|||
attach = attach.trim(); |
|||
this[attach] = node.retrieve("widget") || node; |
|||
this.debug("[Templated:makeAttachPoints] " + attach, node); |
|||
this._attachPoints.push({ node: node, name: attach }); |
|||
}, this); |
|||
node.set(dataWidgetAttachPoint, ""); |
|||
node.set("data-widgetized-attach-point", points); |
|||
} |
|||
}, |
|||
|
|||
// Makes attachevents
|
|||
makeAttachEvents: function(node) { |
|||
// Temporarily store this widget's events so they may be added to this.domNode later
|
|||
var events = node.get(dataWidgetAttachEvent); |
|||
// If there are events....
|
|||
if(events) { |
|||
// For every event found....
|
|||
events.trim().split(",").each(function(event) { |
|||
// Trim the event
|
|||
event = event.trim(); |
|||
// Split the event:method pair
|
|||
var eventFn = event.split(":"); |
|||
// Trim and rename each piece
|
|||
var nativeEvent = eventFn[0].trim(), |
|||
classEvent = eventFn[1].trim(); |
|||
this.debug("[Templated:makeAttachEvents] " + nativeEvent + " / " + classEvent,node); |
|||
|
|||
// If the method isn't found on this, create a stub for it'
|
|||
if(!this[classEvent]) { |
|||
this.debug("cant find ", classEvent, " in: ", this, " creating sub for it"); |
|||
this[classEvent] = function(){}; |
|||
} |
|||
|
|||
// Bind "this" to the event
|
|||
var ev = this[classEvent].bind(this); |
|||
|
|||
// Add the event to the domNode
|
|||
node.addEvent(nativeEvent, ev); |
|||
|
|||
// Store the event
|
|||
this._attachEvents.push({ type: nativeEvent, event: ev, node: node }); |
|||
}, this); |
|||
|
|||
// Remove the event from its former place and add to -ized data item
|
|||
var set = { |
|||
"data-widgetized-attach-event": events |
|||
}; |
|||
set[dataWidgetAttachEvent] = ""; |
|||
node.set(set); |
|||
//node.set("data-widget-attach-event","");
|
|||
//node.set("data-widgetized-attach-event",events);
|
|||
} |
|||
}, |
|||
|
|||
// Destroy: removes node events
|
|||
destroy: function() { |
|||
// Get reference to domNode
|
|||
var domNode = this.domNode, events = this._attachEvents, points = this._attachPoints; |
|||
|
|||
// Clear out children
|
|||
domNode.getElements("[" + dataWidgetized + "]").each(function(widget) { |
|||
widget.destroy(); |
|||
}); |
|||
|
|||
// Remove events
|
|||
if(events.length) { |
|||
events.each(function(event) { |
|||
if(event.node) event.node.removeEvents(); |
|||
}); |
|||
} |
|||
// Remove node connections
|
|||
if(points.length) { |
|||
points.each(function(point) { |
|||
if(point.name != "domNode") this[point.name] = null; |
|||
}, this); |
|||
} |
|||
// Destroy the dom node and its children, fin
|
|||
domNode.store("widget", null).destroy(); |
|||
}, |
|||
|
|||
// Gets the in-node properties for a widget
|
|||
getNodeProps: function(node) { |
|||
var props = node.get(dataWidgetProps), |
|||
widgetProps = {}; |
|||
// Create the widget
|
|||
if(props) { |
|||
// Not using JSON.parse because it's too restricting, especially with quotes
|
|||
var json = "{" + props.trim() + "}"; |
|||
if(JSON && JSON.decode) { // MooTools
|
|||
widgetProps = JSON.decode(json); |
|||
} |
|||
else { // Native
|
|||
eval("widgetProps = " + json); |
|||
} |
|||
} |
|||
return widgetProps; |
|||
}, |
|||
|
|||
debug: function(one, two, three, four) { |
|||
if(this.options.debugMode && console && console.log) { |
|||
console.log("[" + (this.domNode ? this.domNode.id : "") + "] ", one, two || "", three || "", four || ""); |
|||
} |
|||
} |
|||
}); |
|||
|
|||
// If Request is available....
|
|||
if(Request) { |
|||
Templated.templates = {}; |
|||
// Return the template
|
|||
Templated.implement({ |
|||
// Method to return cached template or retrieve new one synchronously
|
|||
getTemplate: function() { |
|||
/* |
|||
var url = this.options.templateUrl; |
|||
// Try to return cached first
|
|||
if(Templated.templates[url]) { |
|||
return Templated.templates[url]; |
|||
} |
|||
else { |
|||
// Send a new request
|
|||
return new Request({ |
|||
url: url, |
|||
async: false, // Used to ensure that necessary templates are there
|
|||
onSuccess: function(template) { |
|||
this.options.template = template; |
|||
Templated.templates[url] = template; |
|||
this.parse(); |
|||
}.bind(this) |
|||
}).send(); |
|||
} |
|||
*/ |
|||
|
|||
var url = this.options.templateUrl; |
|||
// Try to return cached first
|
|||
if(!Templated.templates[url]) { |
|||
// Send a new request
|
|||
return new Request({ |
|||
url: url, |
|||
async: false, // Used to ensure that necessary templates are there
|
|||
onSuccess: function(template) { |
|||
this.options.template = template; |
|||
Templated.templates[url] = template; |
|||
this.parse(); |
|||
}.bind(this), |
|||
onFailure: function() { |
|||
Templated.templates[url] = Templated.prototype.options.template; |
|||
} |
|||
}).send(); |
|||
} |
|||
return Templated.templates[url]; |
|||
} |
|||
}); |
|||
} |
|||
|
|||
// Allow for parsing of an element and its children
|
|||
Element.implement({ |
|||
parse: function() { |
|||
|
|||
var elFn = function(element) { |
|||
// Get the widget type
|
|||
var klass = element.get(dataWidgetType); |
|||
// If the class exists....
|
|||
if(klass && scope[klass]) { |
|||
// Create the new class instance
|
|||
new scope[klass]({ element: element }); |
|||
} |
|||
else { |
|||
window.console && console.log && console.log("klass does not exist! ", klass); |
|||
} |
|||
}; |
|||
|
|||
// Grab this and all nodes which are not already widgetized
|
|||
elFn(this); |
|||
$$(this.getElements("["+ dataWidgetType +"]:not(" + dataWidgetized + ")")).each(elFn); |
|||
} |
|||
}); |
|||
|
|||
// Get widget from id
|
|||
document.widget = function(idOrNode) { |
|||
return document.id(idOrNode).retrieve("widget") || null; |
|||
}; |
|||
|
|||
|
|||
})(this); // Scope limiter
|
@ -0,0 +1,96 @@ |
|||
var AboutSettingTab = new Class({ |
|||
|
|||
tab: '', |
|||
content: '', |
|||
|
|||
initialize: function(){ |
|||
var self = this; |
|||
|
|||
App.addEvent('load', self.addSettings.bind(self)) |
|||
|
|||
}, |
|||
|
|||
addSettings: function(){ |
|||
var self = this; |
|||
|
|||
self.settings = App.getPage('Settings') |
|||
self.settings.addEvent('create', function(){ |
|||
var tab = self.settings.createTab('about', { |
|||
'label': 'About', |
|||
'name': 'about' |
|||
}); |
|||
|
|||
self.tab = tab.tab; |
|||
self.content = tab.content; |
|||
|
|||
self.createAbout(); |
|||
|
|||
}) |
|||
|
|||
}, |
|||
|
|||
createAbout: function(){ |
|||
var self = this; |
|||
|
|||
var millennium = new Date(2008, 7, 16), |
|||
today = new Date(), |
|||
one_day = 1000*60*60*24; |
|||
|
|||
self.settings.createGroup({ |
|||
'label': 'About CouchPotato' |
|||
}).inject(self.content).adopt( |
|||
new Element('div.shutdown').adopt( |
|||
new Element('a.button.red', { |
|||
'text': 'Shutdown', |
|||
'events': { |
|||
'click': App.shutdown.bind(App) |
|||
} |
|||
}), |
|||
new Element('a.button.orange', { |
|||
'text': 'Restart', |
|||
'events': { |
|||
'click': App.restart.bind(App) |
|||
} |
|||
}) |
|||
), |
|||
new Element('div.usenet').adopt( |
|||
new Element('span', { |
|||
'text': 'Help support CouchPotato and save some money for yourself by signing up for an account at' |
|||
}), |
|||
new Element('a', { |
|||
'href': 'https://usenetserver.com/partners/?a_aid=couchpotato&a_bid=3f357c6f', |
|||
'text': 'UsenetServer' |
|||
}), |
|||
new Element('a', { |
|||
'href': 'http://www.newshosting.com/partners/?a_aid=couchpotato&a_bid=a0b022df', |
|||
'text': 'Newshosting' |
|||
}), |
|||
new Element('span', { |
|||
'text': 'For as low as $7.95 per month, you’ll get:' |
|||
}), |
|||
new Element('ul').adopt( |
|||
new Element('li[text=Unlimited downloads]'), |
|||
new Element('li[text=Uncapped speeds]'), |
|||
new Element('li[text=Free SSL Encrypted connections]'), |
|||
new Element('li', { |
|||
'text': Math.ceil((today.getTime()-millennium.getTime())/(one_day))+" days retention" |
|||
}) |
|||
) |
|||
), |
|||
new Element('div.donate', { |
|||
'html': '<form action="https://www.paypal.com/cgi-bin/webscr" method="post"> |
|||
<input type="hidden" name="cmd" value="_s-xclick"> |
|||
<input type="hidden" name="encrypted" value="-----BEGIN PKCS7-----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-----END PKCS7-----"> |
|||
<input type="image" src="https://www.paypal.com/en_US/i/btn/btn_donate_LG.gif" border="0" name="submit" alt="PayPal - The safer, easier way to pay online!"> |
|||
</form> |
|||
I'm building CouchPotato in my spare time, so if you want to buy me a Pepsi while I'm coding, that would be awesome!' |
|||
}) |
|||
); |
|||
|
|||
} |
|||
|
|||
}); |
|||
|
|||
window.addEvent('domready', function(){ |
|||
window.About = new AboutSettingTab(); |
|||
}); |
Loading…
Reference in new issue