scrollIntoView causes WKWebView to be partially unresponsive - wkwebview

I'm having a problem with my hybrid iOS application, in iOS 11 beta. It is basically a web app running within a WKWebView. The problem does not exist in iOS 10, but has been introduced with iOS 11. As of beta 8, the problem is still reproducible.
When the webapp calls the JavaScript function scrollIntoView a couple of times, parts of the GUI becomes unresponsive.
The following reduced HTML file demonstrates the problem:
<!DOCTYPE html>
<html><head>
<meta charset="UTF-8">
<meta id="viewportMeta" name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no">
<title>Testcase</title>
<script>
let numClicks = 0;
function runScrollIntoView() {
document.querySelector(".element-1").scrollIntoView();
}
function debugInfo() {
return {
"window.screen.height": window.screen.height,
"window.screen.availHeight": window.screen.availHeight,
"document.body.scrollTop": document.body.scrollTop,
"document.body.scrollHeight": document.body.scrollHeight
};
}
function clickHandler() {
numClicks++;
document.querySelector('.counter').innerText += numClicks + " clicks, " + JSON.stringify(debugInfo()) + "\n";
}
</script>
<style>
.instructions div {
border: 1px solid blue;
color: blue;
}
.counter {
width: 300px;
height: 300px;
border: 1px solid black;
}
</style>
</head>
<body>
<div class="instructions">
<div onclick="clickHandler()">1. Click me to run click handler</div>
<div onclick="runScrollIntoView()">2. Click me to run Element.scrollIntoView</div>
<div onclick="clickHandler()">3. Click me to run click handler again</div>
<div onclick="runScrollIntoView()">4. Click me to run Element.scrollIntoView again</div>
<div onclick="clickHandler()">5. Click me to try run click handler again - nothing happens</div>
<div>(Repeat 4-5 if necessary)</div>
</div>
<div class="counter">
Number of clicks
</div>
<div class="element-1">Target to scrollIntoView</div>
</body>
</html>
... when being loaded with this viewcontroller:
import UIKit
import WebKit
import Foundation
import SystemConfiguration
class ViewControllerNgf: UIViewController, WKNavigationDelegate, WKUIDelegate {
var webView: WKWebView!
override func loadView() {
self.webView = WKWebView(frame: CGRect.zero)
webView.navigationDelegate = self
webView.uiDelegate = self
view = webView
}
override func viewDidLoad() {
super.viewDidLoad()
self.loadWebApp()
}
fileprivate func loadWebApp() {
let htmlFile = Bundle.main.path(forResource: "htmlfile", ofType: "html")
let html = try? String(contentsOfFile: htmlFile!, encoding: String.Encoding.utf8)
DispatchQueue.main.async {
self.webView!.loadHTMLString(html!, baseURL: nil)
}
}
}
I have reported this as a bug to Apple, but no response yet, leading me to think I'm doing something wrong in the app. But the Swift code is basically copied from Apple's documentation.
So what could be the problem?

The problem seems to have been resolved in iOS 11.2.

Related

Clear storage data from popup when close browser

I want develop ( Chrome Extension with ) a popup with persistent data when close popup, but I want delete storage data when close browser.
This example save current status of button [start/stop] , but I want reset storage data when close the browser, how to delete storage data when close browser ?
Goal about this code example
When close popup save correctly status ( work )
When close browser, clean current status and set to start ( I need to implement )
I read this solution, but is not clear how to implement in my case
let button = document.querySelector('button');
let options = document.querySelector("#btn-start");
chrome.storage.local.get(['button'], function(status) {
console.log('Value currently is ' + status.button);
console.log(typeof(status.button))
if ( status.button === 'Stop') {
setButtonStop();
}
});
button.addEventListener("click", changeButtonStatus);
function changeButtonStatus(){
let buttonStatus = document.querySelector("button").innerText;
let status = '';
if ( buttonStatus === 'Start')
status = setButtonStop();
if ( buttonStatus === 'Stop')
status = setButtonStart();
chrome.storage.local.set({button: status }, function() {
console.log('Value is set to ' + [status]);
});
}
function setButtonStop () {
options.setAttribute("id", "btn-stop");
button.innerText = "Stop";
return button.innerText;
}
function setButtonStart () {
options.setAttribute("id", "btn-start");
button.innerText = "Start";
return button.innerText;
}
a:link,
a:visited,
a:hover,
a:active {
text-decoration: none;
}
/* Popup Window */
#modal-popup {
display: flex;
flex-direction: column;
background: gainsboro;
}
#btn-start button {
width: 100%;
background: green;
color:white;
}
#btn-start button:active {
border-color: green;
background: rgb(0, 255, 0);
color: black;
}
#btn-stop button {
width: 100%;
background: red;
color:white;
}
#btn-stop button:active {
border-color: red;
background: rgb(148, 0, 0);
color: rgb(255, 255, 255);
}
#btn-options button {
width: 100%;
background: white;
border: 0;
}
#btn-options button:active {
background-color: transparent;
animation: rotate 5s infinite;
}
#keyframes rotate {
0% {
transform: rotate(-45deg)
}
100% {
transform: rotate(45deg)
}
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="app.css">
<title>Popup</title>
</head>
<body>
<div id="modal-popup">
<div id="btn-start"><button type="button" name="control">Start</button></div>
<div id="btn-options"><button type="button" name="options">⚙️</button><div>
</div>
<script src="popup.js"></script>
</body>
</html>
{
"name": "popup tutorial #1",
"description": "persistent data settings",
"version": "0.0.1",
"manifest_version": 3,
"permissions": ["storage"],
"action": {
"default_popup": "popup.html",
"default_icon": {
"16": "/images/16.png",
"32": "/images/32.png",
"48": "/images/48.png",
"128": "/images/128.png"
}
},
"icons": {
"16": "/images/16.png",
"32": "/images/32.png",
"48": "/images/48.png",
"128": "/images/128.png"
}
}
use chrome.storage.session instead chrome.storage.local

Guide me on WebRTC PeerJS

I have wrong output of peerjs webrtc call. can anyone help me on how to show the remote and local video chat in the same screen? currently it is showing only one video chat user. Also, I want the design view like this.
https://drive.google.com/file/d/1KPnoEBanIUuThrPuavL1bnqIlG5yjC0o/view?usp=sharing
Can anyone help me by share the total code of this picture UX design view?
My project server is on peerjs cloud server, so no need to put hard coded ip address of 2 peer hosts.
The project has 3 files. main.js, index.html and style.css. Here is the code for main.js .
const peer = new Peer();
var currentCall;
peer.on("open", function (id) {
document.getElementById("uuid").textContent = id;
});
async function callUser() {
// get the id entered by the user
const peerId = document.querySelector("input").value;
// grab the camera and mic
const stream = await navigator.mediaDevices.getUserMedia({
video: true,
audio: true,
});
// switch to the video call and play the camera preview
document.getElementById("menu").style.display = "none";
document.getElementById("live").style.display = "block";
document.getElementById("local-video").srcObject = stream;
document.getElementById("local-video").play();
// make the call
const call = peer.call(peerId, stream);
call.on("stream", (stream) => {
document.getElementById("remote-video").srcObject = stream;
document.getElementById("remote-video").play();
});
call.on("data", (stream) => {
document.querySelector("#remote-video").srcObject = stream;
});
call.on("error", (err) => {
console.log(err);
});
call.on('close', () => {
endCall()
})
// save the close function
currentCall = call;
}
peer.on("call", (call) => {
if (confirm(`Accept call from ${call.peer}?`)) {
// grab the camera and mic
navigator.mediaDevices
.getUserMedia({ video: true, audio: true })
.then((stream) => {
// play the local preview
document.querySelector("#local-video").srcObject = stream;
document.querySelector("#local-video").play();
// answer the call
call.answer(stream);
// save the close function
currentCall = call;
// change to the video view
document.querySelector("#menu").style.display = "none";
document.querySelector("#live").style.display = "block";
call.on("stream", (remoteStream) => {
// when we receive the remote stream, play it
document.getElementById("remote-video").srcObject = remoteStream;
document.getElementById("remote-video").play();
});
})
.catch((err) => {
console.log("Failed to get local stream:", err);
});
} else {
// user rejected the call, close it
call.close();
}
});
function endCall() {
// Go back to the menu
document.querySelector("#menu").style.display = "block";
document.querySelector("#live").style.display = "none";
// If there is no current call, return
if (!currentCall) return;
// Close the call, and reset the function
try {
currentCall.close();
} catch {}
currentCall = undefined;
}
Here is code for style.css.
#live {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
width: 100%;
height: 100%;
background-color: #000;
display: none;
}
#local-video {
position: absolute;
bottom: 0;
left: 0;
width: 250px;
-webkit-transform: scaleX(-1);
transform: scaleX(-1);
margin: 16px;
border: 2px solid #fff;
}
#remote-video {
position: absolute;
max-width: 100%;
height: 100%;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
#end-call {
position: absolute;
bottom: 0;
right: 0;
padding: 8px;
background-color: red;
color: white;
border: none;
margin: 16px;
}
Here is code for index.html .
<!DOCTYPE html>
<html lang="en">
<head>
<title>P2P Video Chat</title>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script src="https://unpkg.com/peerjs#1.3.1/dist/peerjs.min.js"></script>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<!-- App code -->
<div id="menu">
<p>Your ID:</p>
<p id="uuid"></p>
<input type="text" placeholder="Peer id" />
<button onclick="callUser()">Connect</button>
</div>
<div id="live">
<video id="remote-video"></video>
<video id="local-video" muted="true"></video>
<button id="end-call" onclick="endCall()">End Call</button>
</div>
<div id="menu">
<p>Your ID:</p>
<p id="uuid"></p>
<input type="text" placeholder="Peer id" />
<button onclick="callUser()">Connect</button>
</div>
<script src="main.js"></script>
</body>
</html>
Any one come with
complete solution and help by working code links
that is >>compatible on All modern browsers>> is much appreciated. what do you think, which one active/compatible on all browsers >> opensip or peerjs ?

Lit-element children component not re rendering JS code based on updated props

Lit-Element updated .props not invoking full re-render of child component, i.e. the javascript code inside firstUpdated() of child constructs a leaflet map based on .props being passed in from parent component, when the parent component updates location and city, it doesn't create a re-render of the map with new location and city.
When user clicks on button to update location from Seattle to Toronto, the parents props are updated and passed to child, however, the map doesn't rebuild itself, how do I force the map to be "rebuilt" (re-rendered) based on the new .props being passed into the child ??
Git repo for my working sample code
THANKS! :) Been struggling with this for days on end - FYI new to Lit-Element.
Index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link type="text/css" href="./styles.css"
<script src="./webcomponents/webcomponents-loader.js"></script>
<script>
if (!window.customElements) { document.write('<!--'); }
</script>
<script src="./webcomponents/custom-elements-es5-adapter.js"></script>
<!-- ! DO NOT REMOVE THIS COMMENT, WE NEED ITS CLOSING MARKERS -->
</head>
<body>
<app-view></app-view>
</body>
</html>
Index.js:
import './views/app-view.js'
Parent component app-view.js
import { html, LitElement } from 'lit-element';
import './esri-map.js'
class AppView extends LitElement{
static get properties() {
return {
location: { type: Object },
city: { type: String }
}
}
constructor() {
super();
// set fallback location to seattle -- GET will pull in coordinates for Toronto
this.location = { lat: "47.608013", long: "-122.335167" }
this.city = "Seattle"
}
render() {
return html`
<style>
#app-container{
width: 100%,;
height: 100%;
display: flex;
}
#map-container{
flex-grow: 1;
height: 800px;
}
</style>
<button #click=${ (event) => this.updateLocation() }
>Set to Toronto
</button>
<div id="app-container">
<div id="map-container">
<esri-map
.location=${this.location}
.city=${this.city}
>
</esri-map>
</div>
</div>
`;
}
updateLocation(){
var oldLocation = this.location;
this.location = { lat: "43.651070", long: "-79.347015"} // Set to Toronto
this.city = "Toronto"; // Set to Toronto
console.log("New location is: " + this.city)
console.log("Coordinates: ");
console.log(this.location);
}
}
customElements.define('app-view', AppView);
Child Component
import { html, LitElement } from 'lit-element';
import * as L from 'leaflet';
import * as esri from 'esri-leaflet';
class EsriMap extends LitElement{
static get properties() {
return {
location: { type: Object }, // prop passed from parent
city: { type: String } // prop passed from parent
}
}
render() {
return html`
<link rel="stylesheet" href="https://unpkg.com/leaflet#1.6.0/dist/leaflet.css"/>
<style>
#map{
height: 100%;
width: 100%;
}
</style>
<h2>Current City: ${this.city} </h2>
<h3>Coordinates: Lat: ${this.location.lat}</h3>
<div id="map"></div>
`
}
firstUpdated() {
const mapNode = this.shadowRoot.querySelector('#map');
// Render map with props from parent component
var map = L.map(mapNode, {
maxZoom: 18,
minZoom: 2,
}).setView([this.location.lat, this.location.long],8); // [Lat,Lng]
const esriLayer = esri.basemapLayer('Streets');
map.addLayer(esriLayer);
// Render circle with props from parent component
var circle = L.circle([this.location.lat, this.location.long], {
color: 'red',
fillColor: '#f03',
fillOpacity: 0.5,
radius: 20000,
}).addTo(map);
}
}
customElements.define('esri-map', EsriMap);
When properties change, the render() function gets invoked.
firstUpdated() gets only invoked after the first update and not on every property change.
Try this:
updated(changedProps) {
if (changedProps.has('location')) {
this._setMap();
}
}
_setMap() {
const mapNode = this.shadowRoot.querySelector('#map');
if (mapNode == null || this.location == null) {
return;
}
// Render map with props from parent component
var map = L.map(mapNode, {
maxZoom: 18,
minZoom: 2,
}).setView([this.location.lat, this.location.long],8); // [Lat,Lng]
const esriLayer = esri.basemapLayer('Streets');
map.addLayer(esriLayer);
// Render circle with props from parent component
var circle = L.circle([this.location.lat, this.location.long], {
color: 'red',
fillColor: '#f03',
fillOpacity: 0.5,
radius: 20000,
}).addTo(map);
}

web audio input doesnt exist in chrome's flags

I am working on a project about speech recognition, but my html file cannot find microphone.I mean that when I click the button "start recognition " it should pop a bar for allow or deny access to the microphone, but it doesn't.I figure out that I should enable web audio input from chrome://flags, but in chrome://flags there is no option for web audio input.
what should I do?
please help me.
<!DOCTYPE html>
<!--
To change this license header, choose License Headers in Project Properties.
To change this template file, choose Tools | Templates
and open the template in the editor.
-->
<html>
<head>
<title>Web Speech API</title>
<style type="text/css">
#controls {
text-align: center;
}
#start_button {
font-size: 16pt;
}
#transcript {
color: darkred;
font-size: 16pt;
border: 1px solid #ccc;
padding: 5px;
text-align: center;
}
#instructions {
color: darkblue;
font-size: 14pt;
text-align: center;
}
</style>
<script src="http://code.jquery.com/jquery-latest.min.js"></script>
</head>
<body>
<div id="transcript"> </div>
<br>
<div id="instructions"> </div>
<div id="controls">
<button id="start_button">Click to Start</button>
</div>
<script type="text/javascript">
var finalTranscript = '';
var recognizing = false;
$(document).ready(function() {
// check that your browser supports the API
if (!('webkitSpeechRecognition' in window)) {
alert("Sorry, your Browser does not support the Speech API");
} else {
// Create the recognition object and define the event handlers
var recognition = new webkitSpeechRecognition();
recognition.continuous = true; // keep processing input until stopped
recognition.interimResults = true; // show interim results
recognition.lang = 'en-GB'; // specify the language
recognition.onstart = function() {
recognizing = true;
$('#instructions').html('Speak slowly and clearly');
$('#start_button').html('Click to Stop');
};
recognition.onerror = function(event) {
console.log("There was a recognition error...");
};
recognition.onend = function() {
recognizing = false;
$('#instructions').html(' ');
};
recognition.onresult = function(event) {
var interimTranscript = '';
// Assemble the transcript from the array of results
for (var i = event.resultIndex; i < event.results.length; ++i) {
if (event.results[i].isFinal) {
finalTranscript += event.results[i][0].transcript;
//me sunarthsh js na to kopseis kai na pareis tis lekseis kai na allazeis auta pou thes
} else {
//console.log("event.results[i][0].transcript: " + event.results[i][0].transcript);
//if (event.results[i][0].transcript == "equals") {
//... = "=";
//}
//else if (... == "by") {
//... = "*";
//}
//else if (... == "divide") {
//... = "/";
//}
//else if (... == "square") {
//... = "^2";
//}
interimTranscript += event.results[i][0].transcript;
}
}
console.log("interim: " + interimTranscript);
console.log("final: " + finalTranscript);
// update the page
if(finalTranscript.length > 0) {
$('#transcript').html(finalTranscript);
recognition.stop();
$('#start_button').html('Click to Start Again');
recognizing = false;
}
};
$("#start_button").click(function(e) {
e.preventDefault();
if (recognizing) {
recognition.stop();
$('#start_button').html('Click to Start Again');
recognizing = false;
} else {
finalTranscript = '';
// Request access to the User's microphone and Start recognizing voice input
recognition.start();
$('#instructions').html('Allow the browser to use your Microphone');
$('#start_button').html('waiting');
$('#transcript').html(' ');
}
});
}
});
</script>
</body>
</html>
You need that page to be served by a webserver, but it sounds like you're just trying to open the HTML file with your browser. Chrome will block access to the camera/microphone unless the page was served from a webserver. You can create a local server on your machine to serve the page using the command python -m SimpleHTTPServer in the folder with your html file. (assuming you're on a MacOS/linux system).
See this answer for more information
WebRTC - Browser doesn't ask for mic access permission for local html file

While rendering html+js page in node.js using jade, getting html as text rendered instead of the html

I am trying to render an html+js page in nodejs using jade. I used the online available html to jade converters to convert my html to jade and then render it from my nodejs app. However, I am not able to render the jade properly.
The example that I used is the one provided by google in their documentation for google maps API
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Marker animations with google</title>
<style>
html, body, #map-canvas {
height: 100%;
margin: 0px;
padding: 0px
}
#panel {
position: absolute;
top: 5px;
left: 50%;
margin-left: -180px;
z-index: 5;
background-color: #fff;
padding: 5px;
border: 1px solid #999;
}
</style>
<script src="https://maps.googleapis.com/maps/api/js?v=3.exp"></script>
<script>
var berlin = new google.maps.LatLng(52.520816, 13.410186);
var neighborhoods = [
new google.maps.LatLng(52.511467, 13.447179),
new google.maps.LatLng(52.549061, 13.422975),
new google.maps.LatLng(52.497622, 13.396110),
new google.maps.LatLng(52.517683, 13.394393)
];
var markers = [];
var iterator = 0;
var map;
function initialize() {
var mapOptions = {
zoom: 12,
center: berlin
};
map = new google.maps.Map(document.getElementById('map-canvas'),
mapOptions);
}
function drop() {
for (var i = 0; i < neighborhoods.length; i++) {
setTimeout(function() {
addMarker();
}, i * 200);
}
}
function addMarker() {
markers.push(new google.maps.Marker({
position: neighborhoods[iterator],
map: map,
draggable: false,
animation: google.maps.Animation.DROP
}));
iterator++;
}
google.maps.event.addDomListener(window, 'load', initialize);
</script>
</head>
<body>
<div id="panel" style="margin-left: -52px">
<button id="drop" onclick="drop()">Drop Markers</button>
</div>
<div id="map-canvas"></div>
</body>
</html>
I tried many converters to convert but each of them gave me one or the other error. The http://html2jade.vida.io/ was able to convert it into jade.
I saved the file as maps_marker.jade in my views directory and from one of my APIs in web.js I made a call "res.render('maps_marker',{})" to render this page.
However the page rendered the following html as text
<!DOCTYPE html><html><head><meta charset="utf-8"><title>Marker animations with google</title><style><html>, body, #map-canvas {height: 100%;margin: 0px;padding: 0px}</html><div id="panel">{position: absolute;top: 5px;left: 50%;margin-left: -180px;z-index: 5;background-color: #fff;padding: 5px;border: 1px solid #999;}</div></style><script src="https://maps.googleapis.com/maps/api/js?v=3.exp"></script><script><var>berlin = new google.maps.LatLng(52.520816, 13.410186);</var><var>neighborhoods = [</var><new>google.maps.LatLng(52.511467, 13.447179),</new><new>google.maps.LatLng(52.549061, 13.422975),</new><new>google.maps.LatLng(52.497622, 13.396110),</new><new>google.maps.LatLng(52.517683, 13.394393)</new>];<var>markers = [];</var><var>iterator = 0;</var><var>map;</var><function>initialize() {</function><var>mapOptions = {zoom: 12,center: berlin};</var><map>= new google.maps.Map(document.getElementById('map-canvas'),mapOptions);}</map><function>drop() {for (var i = 0; i < neighborhoods.length; i++) {setTimeout(function() {addMarker();}, i * 200);}}</function><function>addMarker() {markers.push(new google.maps.Marker({position: neighborhoods[iterator],map: map,draggable: false,animation: google.maps.Animation.DROP}));iterator++;}</function><google window load initialize class="maps event addDomListener"></google></script></head><body><div id="panel" style="margin-left: -52px"><button id="drop" onclick="drop()">Drop Markers</button></div><div id="map-canvas"></div></body></html>
Can someone help me understand how to render such html+js in node.js.
I am currently try to use the jade template engine, but am open to using any other template engine as well.
UPdated to add snippet from my web.js and input from
I have an index.html(in public folder) with the following form within it
<form id="searchForm" action="/submit_search" method="POST" enctype="multipart/form-data">
<input name="latitude" type="text" id="latitude"/>
<input name="longitude" type="text" id="longitude"/>
<input type="submit" value="Submit" onclick="submitForm()">
</form>
In my javascript - I submit the form.
IN my web.js I have the following snippet for submit_search API
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');
app.post('/submit_search', function(req,res){
console.log(JSON.stringify(req.body));
pg.connect(database_url, function(err, pgclient, done){
if(err)
{
console.log("Error in fetching pgclient from pool");
res.send(500, "Load the error connection page");
}
else if(pgclient != null)
{
console.log("Got an instance of pgclient");
generateDataForMap(req, res, pgclient, done);
}
});
});
And then I have defined my generateDataForMap method
var generateDataForMap = function(req, res, pgclient, done){
... some processing.....
..... create the required json.....
res.render('maps_marker',json);
}
I have put the maps_marker.jade file in the views folder..
The jade file produced by http://html2jade.vida.io/ is not valid. To fix it, you need to add a . (dot char) after the style and script tags to turn them in to style. and script. respectively.
doctype html
html
head
meta(charset="utf-8")
title Marker animations with google
style.
html, body, #map-canvas {
height: 100%;
margin: 0px;
padding: 0px
}
#panel {
position: absolute;
top: 5px;
left: 50%;
margin-left: -180px;
z-index: 5;
background-color: #fff;
padding: 5px;
border: 1px solid #999;
}
script(src="https://maps.googleapis.com/maps/api/js?v=3.exp")
script.
var berlin = new google.maps.LatLng(52.520816, 13.410186);
var neighborhoods = [
new google.maps.LatLng(52.511467, 13.447179),
new google.maps.LatLng(52.549061, 13.422975),
new google.maps.LatLng(52.497622, 13.396110),
new google.maps.LatLng(52.517683, 13.394393)
];
var markers = [];
var iterator = 0;
var map;
function initialize() {
var mapOptions = {
zoom: 12,
center: berlin
};
map = new google.maps.Map(document.getElementById('map-canvas'),
mapOptions);
}
function drop() {
for (var i = 0; i < neighborhoods.length; i++) {
setTimeout(function() {
addMarker();
}, i * 200);
}
}
function addMarker() {
markers.push(new google.maps.Marker({
position: neighborhoods[iterator],
map: map,
draggable: false,
animation: google.maps.Animation.DROP
}));
iterator++;
}
google.maps.event.addDomListener(window, 'load', initialize);
body
#panel(style="margin-left: -52px")
button#drop(onclick="drop()") Drop Markers
#map-canvas

Resources