Commit 0b69fdca authored by addyosmani's avatar addyosmani

merging fidel todo app

parent 7d8888e7
var todoStore = (function() {
return {
get: function() {
var d = localStorage.getItem('fidel.todos');
var todos = {};
if (d) {
d = JSON.parse(d);
for (var key in d) {
todos[key] = new Todo(d[key]);
}
}
return todos;
},
save: function(todos) {
localStorage.setItem('fidel.todos', JSON.stringify(todos));
}
};
})();
var Todo = Fidel.Class.extend({
defaults: {
name: "empty todo...",
done: false,
order: 0
},
init: function() {
},
toggleDone: function() {
this.done = !this.done;
}
});
var TodoView = Fidel.ViewController.extend({
templates: {
item: "#item-template",
stats: '#stats-template'
},
events: {
'keypress input[type="text"]': 'addOnEnter',
'click .check': 'complete'
},
init: function() {
this.todos = todoStore.get();
this.renderAll();
},
addOnEnter: function(e) {
if (e.keyCode == 13)
this.add();
},
add: function() {
var name = this.input.val();
this.input.val('');
var todo = new Todo({ name: name, order: this.taskCount });
this.taskCount++;
this.todos[todo.guid] = todo;
this.save();
var tmp = this.template(this.templates.item, { todo: todo });
this.todosContainer.prepend(tmp);
this.renderStats();
},
save: function() {
todoStore.save(this.todos);
},
sortTasks: function() {
var sorted = [];
for (var key in this.todos) {
sorted.push(this.todos[key]);
}
sorted.sort(function(a, b) {
return (b.order - a.order);
});
return sorted;
},
renderAll: function() {
var html = [];
var todos = this.sortTasks();
this.taskCount = todos.length;
for (var i = 0, c = todos.length; i < c; i++) {
var todo = todos[i];
var tmp = this.template(this.templates.item, { todo: todo });
html.push(tmp);
}
this.todosContainer.html(html.join(''));
this.renderStats();
},
renderStats: function() {
var todos = this.sortTasks();
var data = {
total: todos.length,
remaining: 0,
done: 0
};
for (var i = 0, c = todos.length; i < c; i++) {
var todo = todos[i];
if (todo.done)
data.done++;
else
data.remaining++;
}
this.render('stats', data, this.statsContainer);
},
complete: function(e) {
var complete = (e.target.value == "on");
var el = $(e.target);
el.parents('li').toggleClass('done');
var todoId = el.parents('li').attr('data-todoid');
this.todos[todoId].toggleDone();
this.save();
this.renderStats();
},
clearCompleted: function() {
var completedTodos = this.find('input:checked');
for (var i = 0, c = completedTodos.length; i < c; i++) {
var item = completedTodos[i];
this.destroyTodo($(item));
}
},
destroyTodo: function(el) {
var parent = el.parents('li');
var guid = parent.attr('data-todoid');
delete this.todos[guid];
parent.remove();
this.save();
this.renderStats();
}
});
//app
var todos = new TodoView({ el: $("#todoapp") });
<!DOCTYPE html>
<html>
<head>
<title>Fidel</title>
<link href="stylesheets/todos.css" media="all" rel="stylesheet" type="text/css"/>
<!--<script src="vendor/live.js"></script>-->
</head>
<body>
<!-- Todo App Interface -->
<div id="todoapp">
<div class="title">
<h1>Todos</h1>
</div>
<div class="content">
<div id="create-todo">
<input data-element="input" placeholder="What needs to be done?" type="text" />
<span class="ui-tooltip-top" style="display:none;">Press Enter to save this task</span>
</div>
<div id="todos">
<ul id="todo-list" data-element="todosContainer"></ul>
</div>
<div id="todo-stats" data-element="statsContainer"></div>
</div>
</div>
<div id="credits">
Created by
<br />
<a href="http://jga.me/">Greg Allen</a> (<a href="http://twitter.com/jgaui">jgaui</a>).
</div>
<!-- Templates -->
<script type="text/template" id="item-template">
<li class="todo {!= todo.done ? 'done' : '' !}" data-todoid="{!= todo.guid !}">
<div class="display">
<input class="check" type="checkbox" {!= todo.done ? 'checked="checked"' : '' !} />
<div class="todo-content">{!= todo.name !}</div>
<span data-action="destroyTodo" class="todo-destroy"></span>
</div>
<div class="edit">
<input class="todo-input" type="text" value="" />
</div>
</li>
</script>
<script type="text/template" id="stats-template">
{! if (total) { !}
<span class="todo-count">
<span class="number">{!= remaining !}</span>
<span class="word">{!= remaining == 1 ? 'item' : 'items' !}</span> left.
</span>
{! } !}
{! if (done) { !}
<span class="todo-clear">
<a data-action="clearCompleted">
Clear <span class="number-done">{!= done !}</span>
completed <span class="word-done">{!= done == 1 ? 'item' : 'items' !}</span>
</a>
</span>
{! } !}
</script>
<script src="vendor/json2.js"></script>
<script src="vendor/jquery-1.6.4.min.js"></script>
<script src="vendor/fidel.js"></script>
<!--<script src="../../lib/core.js"></script>-->
<!--<script src="../../lib/class.js"></script>-->
<!--<script src="../../lib/events.js"></script>-->
<!--<script src="../../lib/controller.js"></script>-->
<script src="app.js"></script>
</body>
</html>
This diff is collapsed.
/*!
* Fidel - A javascript controller
* v1.2.2
* https://github.com/jgallen23/fidel
* copyright JGA 2011
* MIT License
*/
!function (name, definition) {
if (typeof module != 'undefined' && module.exports) module.exports = definition();
else if (typeof define == 'function' && typeof define.amd == 'object') define(definition);
else this[name] = definition();
}('Fidel', function() {
var context = this;
var Fidel = {};
Fidel.guid = function(){
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r = Math.random()*16|0, v = c === 'x' ? r : (r&0x3|0x8);
return v.toString(16);
}).toUpperCase();
};
Fidel.extend = function() {
throw new Error("Fidel.extend is deprecated, please use Fidel.ViewController.extend");
};
var o = context.Fidel;
Fidel.noConflict = function() {
context.Fidel = o;
return this;
};
var initializing = false, fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/;
// The base Class implementation (does nothing)
Fidel.Class = function(){};
// Create a new Class that inherits from this class
Fidel.Class.extend = function(prop) {
var _super = this.prototype;
// Instantiate a base class (but only create the instance,
// don't run the init constructor)
initializing = true;
var prototype = new this();
initializing = false;
// Copy the properties over onto the new prototype
for (var name in prop) {
// Check if we're overwriting an existing function
prototype[name] = typeof prop[name] == "function" &&
typeof _super[name] == "function" && fnTest.test(prop[name]) ?
(function(name, fn){
return function() {
var tmp = this._super;
// Add a new ._super() method that is the same method
// but on the super-class
this._super = _super[name];
// The method only need to be bound temporarily, so we
// remove it when we're done executing
var ret = fn.apply(this, arguments);
this._super = tmp;
return ret;
};
})(name, prop[name]) :
prop[name];
}
// The dummy class constructor
function Class(opt) {
// All construction is actually done in the init method
if (!initializing) {
if (this.defaults) {
for (var key in this.defaults) {
if (typeof opt !== 'object' || !opt[key]) this[key] = this.defaults[key];
}
}
if (typeof opt === 'object') {
for (var okey in opt) {
this[okey] = opt[okey];
}
}
if (!this.guid) this.guid = Fidel.guid();
if (this._initialize) this._initialize.apply(this, arguments);
if (this.init) this.init.apply(this, arguments);
}
}
// Populate our constructed prototype object
Class.prototype = prototype;
Class.prototype.proxy = function(func) {
var thisObject = this;
return(function(){
if (!func) return;
return func.apply(thisObject, arguments);
});
};
// Enforce the constructor to be what we expect
Class.constructor = Class;
// And make this class extendable
Class.extend = arguments.callee;
return Class;
};
var cache = {}; //check for "c_" cache for unit testing
//publish("/some/topic", ["a","b","c"]);
Fidel.publish = function(topic, args){
var subs = cache[topic], len = subs ? subs.length : 0;
//can change loop or reverse array if the order matters
while(len--){
subs[len].apply(this, args || []);
}
};
//subscribe("/some/topic", function(a, b, c){ /* handle data */ });
Fidel.subscribe = function(topic, callback){
if(!cache[topic]){
cache[topic] = [];
}
cache[topic].push(callback);
return [topic, callback]; // Array
};
//var handle = subscribe("/some/topic", function(){});
//unsubscribe(handle);
Fidel.unsubscribe = function(handle){
var subs = cache[handle[0]],
callback = handle[1],
len = subs ? subs.length : 0;
while(len--){
if(subs[len] === callback){
subs.splice(len, 1);
}
}
};
Fidel.Class.prototype.on = Fidel.Class.prototype.bind = function(name, callback) {
return Fidel.subscribe(this.guid+"."+name, this.proxy(callback));
};
Fidel.Class.prototype.emit = Fidel.Class.prototype.trigger = function(name, data) {
Fidel.publish(this.guid+"."+name, data);
Fidel.publish(name, data);
};
Fidel.Class.prototype.removeListener = Fidel.Class.prototype.unbind = function(handle) {
Fidel.unsubscribe(handle);
};
!function() {
var templateCache = {};
Fidel.template = function tmpl(template, data) {
var fn = !/\W/.test(template) ?
templateCache[template] = templateCache[template] ||
tmpl(template) :
new Function("obj",
"var p=[],print=function(){p.push.apply(p,arguments);};" +
"with(obj){p.push('" +
template
.replace(/[\r\t\n]/g, "")
.split("{!").join("\t")
.replace(/((^|!})[^\t]*)'/g, "$1\r")
.replace(/\t=(.*?)!}/g, "',$1,'")
.split("\t").join("');")
.split("!}").join("p.push('")
.split("\r").join("\\'")
+ "');}return p.join('');");
return data ? fn( data ) : fn;
};
}();
var eventSplitter = /^(\w+)\s*(.*)$/;
var ViewController = Fidel.Class.extend({
_initialize: function(options) {
if (!this.el) throw "el is required";
this._subscribeHandles = {};
if (this.events) this.delegateEvents();
if (this.elements) this.refreshElements();
if (this.templates) this.loadTemplates();
if (!this.actionEvent) this.actionEvent = "click";
if (this.subscribe) this.bindSubscriptions();
this.delegateActions();
this.getDataElements();
},
template: Fidel.template,
delegateEvents: function() {
for (var key in this.events) {
var methodName = this.events[key];
var match = key.match(eventSplitter);
var eventName = match[1], selector = match[2];
var method = this.proxy(this[methodName]);
if (selector === '') {
this.el.bind(eventName, method);
} else {
this.el.delegate(selector, eventName, method);
}
}
},
delegateActions: function() {
var self = this;
this.el.delegate('[data-action]', this.actionEvent, function(e) {
var el = $(this);
var methodName = el.attr('data-action');
if (self[methodName]) self[methodName].call(self, el, e);
});
},
refreshElements: function() {
for (var key in this.elements) {
this[key] = this.find(this.elements[key]);
}
},
getDataElements: function() {
var self = this;
var elements = this.find("[data-element]");
for (var i = 0, c = elements.length; i < c; i++) {
var name = elements[i].getAttribute('data-element');
if (!self[name]) {
var elem = this.find('[data-element="'+name+'"]');
self[name] = elem;
}
}
},
bindSubscriptions: function() {
for (var key in this.subscribe) {
this._subscribeHandles[key] = Fidel.subscribe(key, this.proxy(this[this.subscribe[key]]));
}
},
loadTemplates: function() {
for (var name in this.templates) {
this.templates[name] = $(this.templates[name]).html();
}
},
find: function(selector) {
return $(selector, this.el[0]);
},
render: function(templateName, data, selector) {
if (arguments.length == 1) {
data = templateName;
templateName = this.primaryTemplate;
}
template = this.templates[templateName];
if (!template) return;
var tmp = this.template(template, data);
selector = (selector)?$(selector):this.el;
selector.html(tmp);
},
destroy: function() {
for (var key in this._subscribeHandles) {
Fidel.unsubscribe(this._subscribeHandles[key]);
}
this.el.undelegate('[data-action]', this.actionEvent);
this.el = null;
}
});
Fidel.ViewController = ViewController;
return Fidel;
});
This diff is collapsed.
This diff is collapsed.
/*
Live.js - One script closer to Designing in the Browser
Written for Handcraft.com by Martin Kool (@mrtnkl).
Version 4.
Recent change: Made stylesheet and mimetype checks case insensitive.
http://livejs.com
http://livejs.com/license (MIT)
@livejs
Include live.js#css to monitor css changes only.
Include live.js#js to monitor js changes only.
Include live.js#html to monitor html changes only.
Mix and match to monitor a preferred combination such as live.js#html,css
By default, just include live.js to monitor all css, js and html changes.
Live.js can also be loaded as a bookmarklet. It is best to only use it for CSS then,
as a page reload due to a change in html or css would not re-include the bookmarklet.
To monitor CSS and be notified that it has loaded, include it as: live.js#css,notify
*/
(function () {
var headers = { "Etag": 1, "Last-Modified": 1, "Content-Length": 1, "Content-Type": 1 },
resources = {},
pendingRequests = {},
currentLinkElements = {},
oldLinkElements = {},
interval = 1000,
loaded = false,
active = { "html": 1, "css": 1, "js": 1 };
var Live = {
// performs a cycle per interval
heartbeat: function () {
if (document.body) {
// make sure all resources are loaded on first activation
if (!loaded) Live.loadresources();
Live.checkForChanges();
}
setTimeout(Live.heartbeat, interval);
},
// loads all local css and js resources upon first activation
loadresources: function () {
// helper method to assert if a given url is local
function isLocal(url) {
var loc = document.location,
reg = new RegExp("^\\.|^\/(?!\/)|^[\\w]((?!://).)*$|" + loc.protocol + "//" + loc.host);
return url.match(reg);
}
// gather all resources
var scripts = document.getElementsByTagName("script"),
links = document.getElementsByTagName("link"),
uris = [];
// track local js urls
for (var i = 0; i < scripts.length; i++) {
var script = scripts[i], src = script.getAttribute("src");
if (src && isLocal(src))
uris.push(src);
if (src && src.match(/\blive.js#/)) {
for (var type in active)
active[type] = src.match("[#,|]" + type) != null
if (src.match("notify"))
alert("Live.js is loaded.");
}
}
if (!active.js) uris = [];
if (active.html) uris.push(document.location.href);
// track local css urls
for (var i = 0; i < links.length && active.css; i++) {
var link = links[i], rel = link.getAttribute("rel"), href = link.getAttribute("href", 2);
if (href && rel && rel.match(new RegExp("stylesheet", "i")) && isLocal(href)) {
uris.push(href);
currentLinkElements[href] = link;
}
}
// initialize the resources info
for (var i = 0; i < uris.length; i++) {
var url = uris[i];
Live.getHead(url, function (url, info) {
resources[url] = info;
});
}
// add rule for morphing between old and new css files
var head = document.getElementsByTagName("head")[0],
style = document.createElement("style"),
rule = "transition: all .3s ease-out;"
css = [".livejs-loading * { ", rule, " -webkit-", rule, "-moz-", rule, "-o-", rule, "}"].join('');
style.setAttribute("type", "text/css");
head.appendChild(style);
style.styleSheet ? style.styleSheet.cssText = css : style.appendChild(document.createTextNode(css));
// yep
loaded = true;
},
// check all tracking resources for changes
checkForChanges: function () {
for (var url in resources) {
if (pendingRequests[url])
continue;
Live.getHead(url, function (url, newInfo) {
var oldInfo = resources[url],
hasChanged = false;
resources[url] = newInfo;
for (var header in oldInfo) {
// do verification based on the header type
var oldValue = oldInfo[header],
newValue = newInfo[header],
contentType = newInfo["Content-Type"];
switch (header.toLowerCase()) {
case "etag":
if (!newValue) break;
// fall through to default
default:
hasChanged = oldValue != newValue;
break;
}
// if changed, act
if (hasChanged) {
Live.refreshResource(url, contentType);
break;
}
}
});
}
},
// act upon a changed url of certain content type
refreshResource: function (url, type) {
switch (type.toLowerCase()) {
// css files can be reloaded dynamically by replacing the link element
case "text/css":
var link = currentLinkElements[url],
html = document.body.parentNode,
head = link.parentNode,
next = link.nextSibling,
newLink = document.createElement("link");
html.className = html.className.replace(/\s*livejs\-loading/gi, '') + ' livejs-loading';
newLink.setAttribute("type", "text/css");
newLink.setAttribute("rel", "stylesheet");
newLink.setAttribute("href", url + "?now=" + new Date() * 1);
next ? head.insertBefore(newLink, next) : head.appendChild(newLink);
currentLinkElements[url] = newLink;
oldLinkElements[url] = link;
// schedule removal of the old link
Live.removeoldLinkElements();
break;
// check if an html resource is our current url, then reload
case "text/html":
if (url != document.location.href)
return;
// local javascript changes cause a reload as well
case "text/javascript":
case "application/javascript":
case "application/x-javascript":
document.location.reload();
}
},
// removes the old stylesheet rules only once the new one has finished loading
removeoldLinkElements: function () {
var pending = 0;
for (var url in oldLinkElements) {
// if this sheet has any cssRules, delete the old link
try {
var link = currentLinkElements[url],
oldLink = oldLinkElements[url],
html = document.body.parentNode,
sheet = link.sheet || link.styleSheet,
rules = sheet.rules || sheet.cssRules;
if (rules.length >= 0) {
oldLink.parentNode.removeChild(oldLink);
delete oldLinkElements[url];
setTimeout(function () {
html.className = html.className.replace(/\s*livejs\-loading/gi, '');
}, 100);
}
} catch (e) {
pending++;
}
if (pending) setTimeout(Live.removeoldLinkElements, 50);
}
},
// performs a HEAD request and passes the header info to the given callback
getHead: function (url, callback) {
pendingRequests[url] = true;
var xhr = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject("Microsoft.XmlHttp");
xhr.open("HEAD", url, true);
xhr.onreadystatechange = function () {
delete pendingRequests[url];
if (xhr.readyState == 4 && xhr.status != 304) {
xhr.getAllResponseHeaders();
var info = {};
for (var h in headers) {
var value = xhr.getResponseHeader(h);
// adjust the simple Etag variant to match on its significant part
if (h.toLowerCase() == "etag" && value) value = value.replace(/^W\//, '');
if (h.toLowerCase() == "content-type" && value) value = value.replace(/^(.*?);.*?$/i, "$1");
info[h] = value;
}
callback(url, info);
}
}
xhr.send();
}
};
// start listening
if (document.location.protocol != "file:") {
if (!window.liveJsLoaded)
Live.heartbeat();
window.liveJsLoaded = true;
}
else if (window.console)
console.log("Live.js doesn't support the file protocol. It needs http.");
})();
\ No newline at end of file
// An example Backbone application contributed by
// [Jérôme Gravel-Niquet](http://jgn.me/). This demo uses a simple
// [LocalStorage adapter](backbone-localstorage.html)
// to persist Backbone models within your browser.
// Load the application once the DOM is ready, using `jQuery.ready`:
$(function(){
// Todo Model
// ----------
// Our basic **Todo** model has `content`, `order`, and `done` attributes.
window.Todo = Backbone.Model.extend({
// Default attributes for the todo.
defaults: {
content: "empty todo...",
done: false
},
// Ensure that each todo created has `content`.
initialize: function() {
if (!this.get("content")) {
this.set({"content": this.defaults.content});
}
},
// Toggle the `done` state of this todo item.
toggle: function() {
this.save({done: !this.get("done")});
},
// Remove this Todo from *localStorage* and delete its view.
clear: function() {
this.destroy();
this.view.remove();
}
});
// Todo Collection
// ---------------
// The collection of todos is backed by *localStorage* instead of a remote
// server.
window.TodoList = Backbone.Collection.extend({
// Reference to this collection's model.
model: Todo,
// Save all of the todo items under the `"todos"` namespace.
localStorage: new Store("todos"),
// Filter down the list of all todo items that are finished.
done: function() {
return this.filter(function(todo){ return todo.get('done'); });
},
// Filter down the list to only todo items that are still not finished.
remaining: function() {
return this.without.apply(this, this.done());
},
// We keep the Todos in sequential order, despite being saved by unordered
// GUID in the database. This generates the next order number for new items.
nextOrder: function() {
if (!this.length) return 1;
return this.last().get('order') + 1;
},
// Todos are sorted by their original insertion order.
comparator: function(todo) {
return todo.get('order');
}
});
// Create our global collection of **Todos**.
window.Todos = new TodoList;
// Todo Item View
// --------------
// The DOM element for a todo item...
window.TodoView = Backbone.View.extend({
//... is a list tag.
tagName: "li",
// Cache the template function for a single item.
template: _.template($('#item-template').html()),
// The DOM events specific to an item.
events: {
"click .check" : "toggleDone",
"dblclick div.todo-content" : "edit",
"click span.todo-destroy" : "clear",
"keypress .todo-input" : "updateOnEnter"
},
// The TodoView listens for changes to its model, re-rendering. Since there's
// a one-to-one correspondence between a **Todo** and a **TodoView** in this
// app, we set a direct reference on the model for convenience.
initialize: function() {
_.bindAll(this, 'render', 'close');
this.model.bind('change', this.render);
this.model.view = this;
},
// Re-render the contents of the todo item.
render: function() {
$(this.el).html(this.template(this.model.toJSON()));
this.setContent();
return this;
},
// To avoid XSS (not that it would be harmful in this particular app),
// we use `jQuery.text` to set the contents of the todo item.
setContent: function() {
var content = this.model.get('content');
this.$('.todo-content').text(content);
this.input = this.$('.todo-input');
this.input.bind('blur', this.close);
this.input.val(content);
},
// Toggle the `"done"` state of the model.
toggleDone: function() {
this.model.toggle();
},
// Switch this view into `"editing"` mode, displaying the input field.
edit: function() {
$(this.el).addClass("editing");
this.input.focus();
},
// Close the `"editing"` mode, saving changes to the todo.
close: function() {
this.model.save({content: this.input.val()});
$(this.el).removeClass("editing");
},
// If you hit `enter`, we're through editing the item.
updateOnEnter: function(e) {
if (e.keyCode == 13) this.close();
},
// Remove this view from the DOM.
remove: function() {
$(this.el).remove();
},
// Remove the item, destroy the model.
clear: function() {
this.model.clear();
}
});
// The Application
// ---------------
// Our overall **AppView** is the top-level piece of UI.
window.AppView = Backbone.View.extend({
// Instead of generating a new element, bind to the existing skeleton of
// the App already present in the HTML.
el: $("#todoapp"),
// Our template for the line of statistics at the bottom of the app.
statsTemplate: _.template($('#stats-template').html()),
// Delegated events for creating new items, and clearing completed ones.
events: {
"keypress #new-todo": "createOnEnter",
"keyup #new-todo": "showTooltip",
"click .todo-clear a": "clearCompleted"
},
// At initialization we bind to the relevant events on the `Todos`
// collection, when items are added or changed. Kick things off by
// loading any preexisting todos that might be saved in *localStorage*.
initialize: function() {
_.bindAll(this, 'addOne', 'addAll', 'render');
this.input = this.$("#new-todo");
Todos.bind('add', this.addOne);
Todos.bind('reset', this.addAll);
Todos.bind('all', this.render);
Todos.fetch();
},
// Re-rendering the App just means refreshing the statistics -- the rest
// of the app doesn't change.
render: function() {
var done = Todos.done().length;
this.$('#todo-stats').html(this.statsTemplate({
total: Todos.length,
done: Todos.done().length,
remaining: Todos.remaining().length
}));
},
// Add a single todo item to the list by creating a view for it, and
// appending its element to the `<ul>`.
addOne: function(todo) {
var view = new TodoView({model: todo});
this.$("#todo-list").append(view.render().el);
},
// Add all items in the **Todos** collection at once.
addAll: function() {
Todos.each(this.addOne);
},
// Generate the attributes for a new Todo item.
newAttributes: function() {
return {
content: this.input.val(),
order: Todos.nextOrder(),
done: false
};
},
// If you hit return in the main input field, create new **Todo** model,
// persisting it to *localStorage*.
createOnEnter: function(e) {
if (e.keyCode != 13) return;
Todos.create(this.newAttributes());
this.input.val('');
},
// Clear all done todo items, destroying their models.
clearCompleted: function() {
_.each(Todos.done(), function(todo){ todo.clear(); });
return false;
},
// Lazily show the tooltip that tells you to press `enter` to save
// a new todo item, after one second.
showTooltip: function(e) {
var tooltip = this.$(".ui-tooltip-top");
var val = this.input.val();
tooltip.fadeOut();
if (this.tooltipTimeout) clearTimeout(this.tooltipTimeout);
if (val == '' || val == this.input.attr('placeholder')) return;
var show = function(){ tooltip.show().fadeIn(); };
this.tooltipTimeout = _.delay(show, 1000);
}
});
// Finally, we kick things off by creating the **App**.
window.App = new AppView;
});
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment