jquery autocomplete not working on dynamically added input boxes - javascript

Here is something i am trying to do. I want to add jquery autocomplete to all the input boxes. Initially i have only 2 boxes, user can dynamically add multiple boxes. Autocomplete works in first two boxes but fails in the dynamically added buttons.
hers the fiddle for the code : http://jsfiddle.net/HG2hP/
code:
HTML:
<!doctype html>
<html>
<head>
<title>TakeMeHome</title>
<!-- <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script> -->
<!--<script type="text/javascript" src="js/jquery-1.9.0.js"></script>
<script type="text/javascript" src="js/jquery-ui-1.10.0.custom.js"></script>
<script type="text/javascript" src="js/jquery-ui-1.10.0.custom.min.js"></script>-->
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false"></script>
<script src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.10.1/jquery-ui.min.js"></script>
<script type="text/javascript" src="js/app.js"></script>
<link type="text/css" rel="stylesheet" href="css/design.css"/>
</head>
<body>
<center>
Your Place:<input class="address" id="source" type="text"/><br/>
<div id= "div1">Friend1:<input class="address" id="friend1" type="text"/></div><br/>
<pre>
<div id="button">Add!</div> <div id="button2">Remove</div></br>
</pre>
<div id="sumbit_button">GO!</div>
<div id="map" style = "width: 500px; height: 500px"></div>
</center>
</body>
</html>
JS
$(document).ready(function() {
var geocoder;
var map;
var marker;
var friends_cnt = 1;
var friends = [];
var distance = [];
$('#button').click(function() {
if (friends_cnt < 11) {
$('<div id = div' + (friends_cnt + 1) + '>Friend' + (friends_cnt + 1) + ':<input type="text" class="address" id="friend' + (friends_cnt + 1) + '"/></div>').insertAfter('#div' + friends_cnt);
friends_cnt++;
} else {
console.log("Limit reached");
}
});
$('#button2').click(function() {
if (friends_cnt > 1) {
$('#friend' + friends_cnt).remove();
$('#div' + friends_cnt).remove();
friends_cnt--;
}
});
geocoder = new google.maps.Geocoder();
$(function() {
$(".address").autocomplete({
source : function(request, response) {
geocoder.geocode({
'address' : request.term
}, function(results, status) {
response($.map(results, function(item) {
return {
label : item.formatted_address,
value : item.formatted_address,
latitude : item.geometry.location.lat(),
longitude : item.geometry.location.lng()
}
}));
})
},
});
});
$('#sumbit_button').on("click", function() {
console.log("button clicked");
var a = [];
a.push($("#source").val());
for ( i = 1; i <= friends_cnt; i++) {
a.push($("#friend" + i).val());
}
console.log("a :");
console.log(a);
var gurl = "http://maps.googleapis.com/maps/api/distancematrix/json";
console.log("gurl :" + gurl);
$.ajax({
url : gurl,
data : {
origins : a.join('|').replace(/ /g, '+'),
destinations : a.join('|').replace(/ /g, '+'),
sensor : false
},
success : function(response) {
console.log("response type :");
console.log( typeof response);
if ( typeof response == "string") {
response = JSON.parse(response);
}
var rows = response.rows;
for (var i = 0; i < rows.length; i++) {
distance[i] = [];
for (var j = 0; j < rows[i].elements.length; j++) {
distance[i][j] = rows[i].elements[j].distance.value;
}
}
}
});
console.log("No.of friends is " + friends_cnt);
console.log(distance);
});
});
CSS
input {
margin: 10px 4px;
}
#button, #button2 {
width: 70%;
margin: 0 auto;
}
.ui-autocomplete {
background-color: white;
width: 300px;
border: 1px solid #cfcfcf;
list-style-type: none;
padding-left: 0px;
}
.ui-helper-hidden-accessible {
display: none;
}
As I am using classes concept to add auto-complete, I should get the results.
Can you please let me know where I am wrong?

In your click event:
$('#button').click(function ()...
You should set your autocomplete on newly added input field, something like:
$('#button').click(function () {
if (friends_cnt < 11) {
$('<div id = div'+(friends_cnt+1)+'>Friend' + (friends_cnt+1) + ':<input type="text" class="address" id="friend' + (friends_cnt+1) + '"/></div>').insertAfter('#div'+friends_cnt);
$('div#div'+(friends_cnt+1)).autocomplete(...);
friends_cnt++;
}
else {
console.log("Limit reached");
}
});
As your autocomplete attachs to current DOM only, not the dynamically added one.

you must either re apply the auto complete to each element like Kapo suggested
$('div#div'+(friends_cnt+1)).autocomplete(...);
or use liveuery (see this thread for more details on that)

On pageload your autocomplete attaches to fields with class .address, however on page load you do not have any .address field, so nothing gets attached.
You need to run
$(".address").autocomplete({
Right after you add your new field to the dom, or better still, make the input an object, and you can attach events straight to it like:
$input = $('input')
.attr('type', 'text')
.attr('class', 'address')
.attr('id', 'friend')
.autocomplete({ etc

As i got the answers, this change in the code worked for me :
Adding this in the $('#button').click(function () { function
$(function() {
$("#friend"+(friends_cnt+1)).autocomplete({
source: function(request, response) { . .. . .
This will add autocomplete to all the boxes whenever made.

Related

Uncaught TypeError: Cannot read property addEventListener of null

Im working on an assignment for my javascript class, and I keep getting the error
fetch_page.js:109 Uncaught TypeError: Cannot read property 'addEventListener' of null
at addEventListeners (fetch_page.js:109)
at fetch_page.js:121
I'll be honost, I don't understand javascript for crap, but I'm forced to take this class for my network admin degree. Can anyone point out where I'm making this error?
window.addEventListener('DOMContentLoaded', (function() {
var contents;
var protocol;
var hostname;
var directory;
var file;
function parseBase() {
var pos, slashPos;
var remainder;
pos = BASE.indexOf('://');
protocol = BASE.substr(0, pos);
remainder = BASE.substr(pos + 3);
slashPos = remainder.indexOf('/');
if (slashPos === -1) {
hostname = remainder;
directory = "";
file = "";
} else {
hostname = remainder.substr(0, slashPos);
remainder = remainder.substr(slashPos + 1);
slashPos = remainder.lastIndexOf('/');
if (slashPos === -1) {
directory = "";
file = remainder;
} else {
directory = remainder.substr(0, slashPos);
file = remainder.substr(slashPos + 1);
}
}
console.log("protocol:", protocol);
console.log("hostname:", hostname);
console.log("directory:", directory);
console.log("file:", file);
}
function relativeToAbsolute(url) {
if (url.indexOf('://') > -1) { // http://somedomain.com/path/file.html
return url;
} else if (url[0] === '/') { // /path/file.html
return protocol + "://" + hostname + url;
} else { // path/file.html
if (directory === "") {
return protocol + "://" + hostname + "/" + url;
} else {
return protocol + "://" + hostname + "/" + directory + "/" + url;
}
}
}
function parsePage() {
var parser = new DOMParser();
contents = parser.parseFromString(atob(PAGE), "text/html");
console.log(contents);
}
function moveChildren(source, destination) {
while (source.childNodes.length > 0) {
var child = source.childNodes[0];
source.removeChild(child);
destination.appendChild(child);
}
}
function fixTags(tagName, attribute) {
var tags = contents.getElementsByTagName(tagName);
for (var counter = 0; counter < tags.length; counter++) {
var url = tags[counter].getAttribute(attribute);
if (url) {
tags[counter].setAttribute(attribute, relativeToAbsolute(url));
}
}
}
function fixRedirectedTags(tagName, attribute) {
var tags = contents.getElementsByTagName(tagName);
for (var counter = 0; counter < tags.length; counter++) {
var url = tags[counter].getAttribute(attribute);
if (url) {
tags[counter].setAttribute(attribute,
window.location.origin + window.location.pathname + '?url=' + encodeURIComponent(relativeToAbsolute(url)));
}
}
}
function fixURLs() {
fixTags('img', 'src');
fixTags('script', 'src');
fixTags('link', 'href');
fixRedirectedTags('a', 'href');
}
function moveContent() {
moveChildren(contents.head, document.head);
moveChildren(contents.body, document.getElementById('contents'));
}
function submit() {
console.log("submitted:", encodeURIComponent(document.getElementById('urlBox').value));
}
function addEventListeners() {
document.getElementById('goButton').addEventListener('click', submit);
document.getElementById('urlBox').addEventListener('keydown', function(event) {
if (event.keyCode == 13 || event.keyCode == 10) {
submit();
}
});
}
return function() {
parseBase();
parsePage();
fixURLs();
moveContent();
addEventListeners();
}
})())
body {
margin: 0px;
}
#searchBox {
background: black;
color: white;
width: 100%;
text-align: center;
vertical-align: middle;
padding: 10px;
}
#goButton {
background: red;
color: black;
padding: 4px
font-weight: bold;
font-family: Arial;
font-size: .75em;
vertical-align: middle;
cursor: pointer;
}
#urlBox {
width: 50%
}
#contents {
border: 1px solid black;
}
<?php
$url = isset ($_GET['url']) ? $_GET['url'] : "http://eloquentjavascript.net/";
$contents = base64_encode(mb_convert_encoding(file_get_contents($url), "HTML-ENTITIES","UTF-8"));
?>
<!doctype html>
<html>
<head>
<title>Fetch Page</title>
<link rel="stylesheet" href="fetch_page.css">
<script src="fetch_page.js"></script>
<script>
var BASE = "<?php echo $url; ?>";
var PAGE = "<?php echo $contents; ?>";
</script>
</head>
<body>
<div id="searchBox">Type a URL here: <input type="text" id=urlBox"> <span id="goButton">GO</span></div>
<div id="tocContainer">
<div id="toc">[toc goes here]</div>
<p id="contents">Hello World!</p>
<div id="contents"></div>
</body>
</html>
What does your error mean?
Uncaught TypeError: Cannot read property 'addEventListener' of null.
at addEventListeners
That boils down to:
You've tried to access the addEventListener property of something in the addEventListeners function, but that's null.
Take a look at addEventListeners:
document.getElementById('goButton').addEventListener('click', submit);
document.getElementById('urlBox').addEventListener('keydown', function(event) {
if (event.keyCode == 13 || event.keyCode == 10) {
submit();
}
});
One of the getElementsById calls has returned null, indicating that there's no such ID in your code.
After looking at the HTML, you can see that the problem is at the following place:
<input type="text" id=urlBox">
You're missing the opening quote at the ID, so really you've given your element the ID of urlBox" (you can omit quotes around a HTML attribute, but not recommended though).
That's why the JS can't find the element with ID urlBox
Just experienced something similar. Adding a defer to my script tag solved the issue:
<script src="fetch_page.js" defer></script>
Good luck! ~E
One of these two document.getElementById calls is returning null, because there's no element with that Id. You can add a breakpoint in the debug console in the code, or add a console.log to figure out exactly where the problem is occuring
document.getElementById('goButton').addEventListener('click', submit);
document.getElementById('urlBox').addEventListener('keydown', function(event) {
Apart from any typing error like eg. omitting quotes, try defer in the <script src ... line of the html code. I solved my problem in this way!
I had this problem too and I checked my code and everything was in other. I realized that it was where I positioned my script tag i.e the head of the html file so I then put it at the end of my body tag. I also found out that from one of the guys who answered this question that using "defer" makes it work no matter where the script tag is as long as it's inside the html tag.
Add window.onload = function() {<enter your javascript code here}; to your script. That easy.

Javascript function .show() not using HTML code - on Page Search

I found some Javascript code that does exactly what I want it to do. It searches a page of <li> for text that you enter in a search box.
However, It does not show (or use) the HTML markup that is within the text.
$(document).ready(function() {
/* initially hide product list items */
$("#dino-list li").hide();
/* highlight matches text */
var highlight = function(string) {
$("#dino-list li.match").each(function() {
var matchStart = $(this).text().toLowerCase().indexOf("" + string.toLowerCase() + "");
var matchEnd = matchStart + string.length - 1;
var beforeMatch = $(this).text().slice(0, matchStart);
var matchText = $(this).text().slice(matchStart, matchEnd + 1);
var afterMatch = $(this).text().slice(matchEnd + 1);
$(this).html(beforeMatch + "<em>" + matchText + "</em>" + afterMatch);
});
};
/* filter products */
$("#search-dinosaurs").on("keyup click input", function() {
if (this.value.length > 0) {
$("#dino-list li").removeClass("match").hide().filter(function() {
return $(this).text().toLowerCase().indexOf($("#search-dinosaurs").val().toLowerCase()) != -1;
}).addClass("match").show();
highlight(this.value);
$("#dino-list").show();
} else {
$("#dino-list, #dino-list li").removeClass("match").hide();
}
});
});
input[type=text] {
width: 200px;
padding: 8px 10px;
}
li em {
background: #ff6;
font-weight: bold;
font-style: normal;
}
<input type="text" id="search-dinosaurs" placeholder="Search for Dinosaurs (start typing)" />
<ul id="dino-list">
<li>Diplo<BR>docus</li>
<li>Stego<FONT COLOR="RED">saurus</FONT>
</li>
<li>Triceratops</li>
<li>Pteradactyl</li>
<li>Tyrannosaurus Rex</li>
<li>Protoceratops</li>
<li>Iguanadon</li>
<li>Velociraptor</li>
</ul>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
In the search box, type: Dipl
You will see the full name, and the <BR> is ignored.
How can I get this code to use the HTML code that is inline with the result?
the result should show:
Diplodocus
Thanks for any thoughts
try this one
$(function(){
$('#dino-list>li').hide();
$('#search-dinosaurs').on('keyup',function(){search(this.value)})
})
function search(txt){
var target = $('#dino-list');
// reset content
var content = target.html();
if (typeof target.data('content')!='undefined') {
target.html(target.data('content'));
} else target.data('content',content);
$('#dino-list>li').hide();
if (txt!='') {
// begin search
$('#dino-list').find('*').contents().each(function() {
if (this.nodeType === 3) {
// wrap text node with 'em'
$(this).replaceWith(this.nodeValue.replace(new RegExp('('+txt+')','gi'),'<em>$1</em>'))
};
});
// display li with em
$('em',target).each(function(){
$(this).closest('li').show();
})
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<input type="text" id="search-dinosaurs" placeholder="Search for Dinosaurs (start typing)" />
<ul id="dino-list">
<li>Diplo<BR>docus</li>
<li>Stego<FONT COLOR="RED">saurus</FONT></li>
<li>Triceratops</li>
<li>Pteradactyl</li>
<li>Tyrannosaurus Rex</li>
<li>Protoceratops</li>
<li>Iguanadon</li>
<li>Velociraptor</li>
</ul>

How to save drag/drop coordinates back to angular model?

My code allows for dragging and dropping of form fields overlaying an page image. I'm using Kendo-ui for the drag/drop but that's not critical to the answer, I don't think, and the demo is overly simplified and doesn't contain the image. I need to be able to change the angular model's coordinates to reflect the dropped location so I can save it. The meat of my question is HOW to update the model. What's the most efficient way of doing this since I can possibly have hundreds of fields? Is it possible to bind to the left/bottom CSS coordinates? Should I update the CSS manually using jQuery and then update the model?
Here's the plunker with my code
INDEX.HTML
<!DOCTYPE html>
<html ng-app="app">
<head>
<title></title>
<link rel="stylesheet" href="https://kendo.cdn.telerik.com/2016.2.714/styles/kendo.common-bootstrap.min.css" />
<link rel="stylesheet" href="https://kendo.cdn.telerik.com/2016.2.714/styles/kendo.bootstrap.min.css" />
<link rel="stylesheet" href="https://kendo.cdn.telerik.com/2016.2.714/styles/kendo.bootstrap.mobile.min.css" />
<script src="https://kendo.cdn.telerik.com/2016.2.714/js/jquery.min.js"></script>
<script src="https://code.angularjs.org/1.5.8/angular.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.8/angular-animate.js"></script>
<script src="https://kendo.cdn.telerik.com/2016.2.714/js/kendo.all.min.js"></script>
<script id="page-template" type="text/ng-template">
<div class="page" kendo-droptarget style="{{ 'width:' + (p.width + 2) + 'px; height:' + (p.height + 2) + 'px;' }}" ng-repeat="p in model.transaction.selectedDocument.pages">
<div class="field" data-fieldname="f.fieldName" kendo-draggable k-hint="model.draggableHint" k-dragstart="model.onDragStart" k-dragend="model.onDragEnd" ng-repeat="f in p.fields" style="{{ 'left:' + f.left + 'px;bottom:' + f.bottom + 'px;width:' + f.width + 'px;height:' + f.height + 'px;' }}">
<div></div>
</div>
</div>
<pre>{{ model | json }}</pre>
</script>
<link rel="stylesheet" href="style.css" />
<script src="script.js"></script>
</head>
<body>
<page-image-component></page-image-component>
</body>
</html>
SCRIPT.JS
// Code goes here
console.clear();
function pageImageController(TransactionFactory) {
var model = this;
model.transaction = TransactionFactory;
model.draggableHint = function (e) {
return e.clone();
}
model.onDragStart = function (e) {
console.log(e);
e.currentTarget.hide();
}
model.onDragEnd = function (e) {
console.log(e);
//e.currentTarget.css("left", "0px").css("top", "0px");
var field = e.currentTarget[0];
console.log(e.currentTarget)
e.currentTarget.show();
}
}
var app = angular.module("app", ["kendo.directives"]);
app.factory('TransactionFactory', function () {
var transaction = {
selectedDocument: {
fileName: "my.pdf",
pages: [{
pageNumber: 1,
width: 400,
height: 500,
fields: [
{
fieldName: "my field 1",
width: 75,
height: 13,
left: 50,
bottom: 300,
instance: 1
},
{
fieldName: "another field 1",
width: 65,
height: 13,
left: 200,
bottom: 440,
instance: 1
},
]
}]
}
};
return transaction;
});
app.component("pageImageComponent", {
template: $("#page-template").html(),
controllerAs: "model",
controller: ["TransactionFactory", pageImageController]
});
STYLE.CSS
/* Styles go here */
.page
{
border: 1px solid black;
position: relative;
}
.field
{
background-color: #ddd;
position: absolute;
}
I think I figured it out and I think it's efficient enough for rock and roll.
Here's the Plunker
function pageImageController(TransactionFactory) {
var model = this;
model.transaction = TransactionFactory;
var tempLeft = 0;
var tempTop = 0;
model.draggableHint = function (e) {
return e.clone();
}
model.onDragStart = function (e) {
//console.log(e);
e.currentTarget.hide();
}
model.onDrop = function (e) {
tempLeft = e.draggable.hint.offset().left -9;
tempTop = e.draggable.hint.offset().top +2;
console.log(e.draggable);
}
model.onDragEnd = function (e) {
//console.log(e);
var field = e.currentTarget[0];
//console.log(field)
var fieldIndex = field.attributes['data-fieldindex'].value;
var pageIndex = field.attributes['data-pageindex'].value;
//console.log(fieldIndex);
//console.log(pageIndex);
var tempBottom = model.transaction.selectedDocument.pages[pageIndex].height - tempTop;
model.transaction.selectedDocument.pages[pageIndex].fields[fieldIndex].left = tempLeft;
model.transaction.selectedDocument.pages[pageIndex].fields[fieldIndex].bottom = tempBottom;
e.currentTarget.css("left", tempLeft + "px").css("bottom", tempBottom + "px");
e.currentTarget.show();
}
}

Phonegap App not Running javascript elements

So I have a code that runs the css and html fine in phonegap, but the javascript items do not function. For example I'm making a To Do app, but the button will not save my new item, or click to delete.
css code
body
{
font-family: Verdana, Arial;
font-size: 18px;
background-color:#D4D0B4;
}
h1
{
background-color:#626b5e;
font-size:1em;
color:#F5F6F5;
line-height:2em;
text-align:center;
}
#newTaskInput, #addNewTask
{
display:block;
width:98%;
margin-top:5px;
margin-left:auto;
margin-right:auto;
background-color:#757769;
border:0;
height;2em;
font-size:1em;
color:#F5F6F5;
}
#taskList
{
margin-top:10px;
}
#taskList > li
{
background: -webkit-linear-gradient(#FFF, #F6F6F7);
background: -o-linear-gradient(#FFF, #F6F6F7);
background: -moz-linear-gradient(#FFF, #F6F6F7);
background: linear-gradient(#FFF, #F6F6F7);
border:1px solid #BBB6AF
line-height:2em;
color:#929292;
margin-top:2px;
}
#taskList span
{
margin-left:5px;
}
.done
{
text-decoration:line-through;
opacity:0.5;
}
HTML
<html>
<head>
<meta charset="utf-8" />
<link rel="stylesheet" type="type/css" href="css/reset.css">
<link rel="stylesheet" type="text/css" href="css/index.css" />
<title>Todo List</title>
</head>
<body>
<h1>Todo List</h1>
<div id="newTaskSection">
<input type="text" id="newTaskInput" placeholder="New Task">
<button id="addNewTask">Add</button>
</div>
<ul id="taskList">
</ul>
<script type="text/javascript" src="js/jquery-2.1.4.min.js"></script>
<script type="text/javascript" src="js/index.js"></script>
</body>
</html>
javascript
var taskList = new Array();
$( document ).ready(function(){
var $newTaskInput = $('#newTaskInput');
var $taskList = $('#taskList');
var taskTouchStart;
var taskTouchEnd;
var taskTouchStartX;
var taskTouchEndX;
if( window.localStorage )
{
taskList = JSON.parse(window.localStorage.getItem('taskList'));
}
if(null !== taskList)
{
for(i=0;i<taskList.length;i++)
{
var newTask = '<li data-key="' + taskList[1].key + '"><span>' + taskList[i].task + '</span></li>';
$taskList.append(newTask);
}
}
else
{
taskList = new Array();
}
$('#addNewTask').on('click', function(){
var key = Date.now();
var newTask = '<li data-key="' + key + '"><span>' + $newTaskInput.val() + '</span></li>';
$taskList.append( newTask );
taskList.push({key:key, task:$newTaskInput.val(), done:false});
if(window.localStorage)
{
window.localStorage.setItem('taskList', JSON.stringify(taskList));
}
$newTaskInput.val('');
});
$taskList.on('touchstart', 'li', function(e){
var start = document.elementFromPoint( e.originalEvent.touches[0].pageX, e.originalEvent.touches[0].pageY);
taskTouchStart = $(start).attr('data-key');
taskTouchStartX = e.originalEvent.touches[0].pageX;
});
$taskList.on('touchend', 'li', function(e){
var $end;
var $this = $(this);
var end = document.elementFromPoint( e.originalEvent.touches[0].pageX, e.originalEvent.touches[0].pageY);
$end = $(end);
taskTouchEnd = $end.attr('data-key');
taskTouchEndX = e.originalEvent.touches[0].pageX;
if(taskTouchStart == taskTouchEnd)
{
if(taskTouchStartX < taskTouchEndX)
{
if($this.hasClass('done'))
{
$this.removeClass('done');
}
else
{
$this.addClass('done');
}
}
else
{
taskList = $.grep(taskList, function(e){ return e.key != taskTouchEnd;});
if(window.localStorage)
{
window.localStorage.setItem('taskList', JSON.stringify(taskList));
}
$end.remove();
}
}
});
});
Simplified form of the done/not done handler in the code below, which would replace your current on('touchstart.. and on('touchend... blocks and remove a lot of complexity:
Additionally, you have a 1 where there should be an i in your display block (unless I mistook the purpose), and you're not setting the done class for tasks from localStorage which are marked such.
Changes commented in the code below, which would replace the JS you posed above.
Also, apologies, but I've mixed jQuery and vanilla JS, and only went so far as to get a working example, you'll have to work further on validation on what not, hopefully this gets you going.
$( document ).ready(function(){
var $newTaskInput = $('#newTaskInput');
var $taskList = $('#taskList');
if( window.localStorage ){
taskList = JSON.parse(window.localStorage.getItem('taskList'));
}
if(null !== taskList){
for(i=0;i<taskList.length;i++){
// Should we add the 'done' class to these items?
var newTaskClass = (taskList[i].done)? 'done': '';
// taskList[1].key to taskList[i].key ??? or am I missing something?
var newTask = '<li data-key="' + taskList[i].key + '" class="' + newTaskClass + '"><span>' + taskList[i].task + '</span></li>';
$taskList.append(newTask);
}
}
else {
taskList = new Array();
}
$('#addNewTask').on('click', function(){
var key = Date.now();
var newTask = '<li data-key="' + key + '"><span>' + $newTaskInput.val() + '</span></li>';
$taskList.append( newTask );
taskList.push({key:key, task:$newTaskInput.val(), done:false});
if(window.localStorage)
{
window.localStorage.setItem('taskList', JSON.stringify(taskList));
}
$newTaskInput.val('');
});
// Replaces the 'touchstart/end' handlers
$(document).on('click', '#taskList li', function(e){
var task = $(this);
// Update the li class
if (task.hasClass('done')){
task.removeClass('done');
} else {
task.addClass('done');
}
// Find the item by its key property (assumes key exists / no duplicates)
var itemToUpdate = taskList.filter(function(item){
return item.key === task.data('key');
})[0];
// If true, make false, if false, make ture
itemToUpdate.done = !itemToUpdate.done;
// Over-write the task list in local storage
window.localStorage.setItem('taskList', JSON.stringify(taskList));
});
});

Offline storage in firefox 3.5 for file://

I was trying out the code for offline storage in firefox 3.5, taken from http://starkravingfinkle.org/blog/2008/05/firefox-3-offline-app-demo-part-2/.
When the page loads i get a dialog prompting me that the application is asking for storing data, but when i press Allow, the dialog does not go away . The app works fine at the online demo given on the website.
The source file containing the javascript is as follows :
todo.html
<!--
Simple task list application used to illusrate Firefox's offline/DOMStorage capabilities
Author: Mark Finkle
-->
<html manifest="todo.manifest">
<head>
<title>TODO - Offline Demo</title>
<script type="text/javascript" src="json.js"></script>
<script language="javascript">
var taskStorage = "[]";
var storageDomain = location.hostname;
if (storageDomain == "localhost")
storageDomain += ".localdomain";
function loaded() {
updateOnlineStatus("load", false);
document.body.addEventListener("offline", function () { updateOnlineStatus("offline", true) }, false);
document.body.addEventListener("online", function () { updateOnlineStatus("online", true) }, false);
if (typeof globalStorage != "undefined") {
var storage = globalStorage[storageDomain];
if (storage && storage.taskStorage) {
taskStorage = storage.taskStorage;
}
}
fetchList();
}
function updateOnlineStatus(msg, allowUpdate) {
var status = document.getElementById("status");
status.innerHTML = (navigator.onLine ? "[online]" : "[offline]");
var log = document.getElementById("log");
log.appendChild(document.createTextNode("Event: " + msg + "\n"));
if (navigator.onLine && allowUpdate) {
update();
log.appendChild(document.createTextNode("Updated server\n"));
}
}
function httpRequest(type, data, callback) {
var httpreq = new XMLHttpRequest();
httpreq.onreadystatechange = function() { if (httpreq.readyState == 4) callback(httpreq.readyState, httpreq.status, httpreq.responseText); };
httpreq.open(type, "todo-server.php", true);
if (type == "POST") {
httpreq.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
}
httpreq.send(data);
}
function loadList(readyState, status, responseText) {
if (readyState == 4) {
if (status == 200) {
taskStorage = responseText;
var tasks = eval("(" + taskStorage + ")");
var html = "";
for (var i=0; i<tasks.length; i++) {
html += "<input type='checkbox' id='" + tasks[i].name + "'/><label for='" + tasks[i].name + "'>" + tasks[i].data + "</label><br/>";
}
document.getElementById("tasklist").innerHTML = html;
if (typeof globalStorage != "undefined") {
globalStorage[storageDomain].taskStorage = taskStorage;
}
}
}
}
function fetchList() {
if (navigator.onLine) {
httpRequest("GET", null, loadList);
}
else {
loadList(4, 200, taskStorage);
}
}
function addItem() {
var data = document.getElementById("data").value;
document.getElementById("data").value = "";
var tasks = eval("(" + taskStorage + ")");
tasks.push({"name": Date.now(), "data": data });
taskStorage = tasks.toJSONString();
update();
}
function removeItems() {
var tasks = eval("(" + taskStorage + ")");
var newTasks = [];
var items = document.getElementById("tasklist").getElementsByTagName("input");
for (var i=0; i<items.length; i++) {
if (items[i].checked == false) {
newTasks.push(tasks[i]);
}
}
taskStorage = newTasks.toJSONString();
update();
}
function completeItems() {
var tasks = eval("(" + taskStorage + ")");
var items = document.getElementById("tasklist").getElementsByTagName("input");
for (var i=0; i<items.length; i++) {
if (items[i].checked) {
var task = tasks[i].data;
if (task.indexOf("<strike>") != -1) {
task = task.replace("<strike>", "");
task = task.replace("</strike>", "");
}
else {
task = "<strike>" + task + "</strike>";
}
tasks[i].data = task;
}
}
taskStorage = tasks.toJSONString();
update();
}
function update() {
if (navigator.onLine) {
var post = "action=update&data=";
post += encodeURIComponent(taskStorage);
httpRequest("POST", post, function(readyState, status, json) { fetchList(); });
}
else {
loadList(4, 200, taskStorage);
}
}
</script>
<style type="text/css">
body { font-family: verdana,tahoma, arial; }
div#container { width: 300px; }
div#title { font-size: 120%; }
div#subtitle { font-size: 80%; }
div#tasklist { margin-bottom: .5em; }
div#log { font-size: 90%; background-color: lightgray; margin-top: 1em; white-space: pre; }
</style>
</head>
<body onload="loaded();">
<div id="container">
<div id="title">Task Helper - <span id="status">ONLINE</span></div>
<div id="subtitle">simple online/offline demo application</div>
<hr />
<div id="tasklist">
</div>
<input type="text" id="data" size="35" />
<input type="button" value="Add" onclick="addItem();"/>
<hr />
<input type="button" value="Remove" onclick="removeItems();"/>
<input type="button" value="Complete" onclick="completeItems();"/>
<div id="log"><strong>Event Log</strong>
</div>
</div>
</body>
</html>
I believe that the localStorage api is replacing globalStorage in FF 3.5. You can read more about it here: https://developer.mozilla.org/en/DOM/Storage
I think the api is very similar, so you could try something like this:
var storage;
if (typeof localStorage != "undefined") {
storage = localStorage;
}
else if (typeof globalStorage != "undefined") {
storage = globalStorage[storageDomain];
}
if (storage && storage.taskStorage) {
taskStorage = storage.taskStorage;
}
Hope that helps!
EDIT: Anywhere you use globalStorage, you'll have to check for localStorage as well. Or promote the storage variable up in scope and detect it once.
So after reading the question twice, I think I understood the question: you're asking about using globalStorage in file:/// documents.
globalStorage (as well as localStorage) doesn't work very well in file:/// documents as of Firefox 3.5. I didn't see the specific bug report about this issue, but since globalStorage is deprecated in favor of localStorage, it doesn't really matter.
If you're just testing it out, install some kind of web server locally, it's not complicated at all.

Categories