where to reference external js file in html button - javascript

I'm doing an exercise from a course in Udacity and they give us an html with its functions in javascript. The webpage consists of a form in which you write something and when you submit it then that is added to the page in a blue square. The point of this is to show us how the mvo design pattern works, so the js file is divided in the model, the view and the octopus which connects the two previous. Just to play around, I wanted to add a "remove" button that removed the last block in the page. I kind of coppied the function that added a new block, but used .pop() instead of .push() in order to manipulate the localstorage of the page. I think the function is correct, but I can't figure out how to "call" the function. I've tried to add an event listener to the button. I also tried to use .submit() from jquery with event.preventDefault(); to put the remove function as a parameter of .submit(). I think the closest option would be to reference the .js with tags and then call the function inside the buttons onclick attribute, but it's not working (the function is a method of an object that is inside the .js, so I tried calling it like this <button onclick = "javascript:octopus.remove()">remove!</button>). Also tried using onclick but on javascript document.getElementById("button").onclick = function(){}; but nothing. Any help? this is the js
$(function(){
var model = {
init: function() {
if (!localStorage.notes) {
localStorage.notes = JSON.stringify([]);
}
},
add: function(obj) {
var data = JSON.parse(localStorage.notes);
data.push(obj);
localStorage.notes = JSON.stringify(data);
},
getAllNotes: function() {
return JSON.parse(localStorage.notes);
},
//here I tried everything but nothing seems to work
remove: function() {
document.getElementById("button").onclick =
function(){
var data = JSON.parse(localStorage.notes);
data.push(obj);
localStorage.notes = JSON.stringify(data);
};
}
};
var octopus = {
addNewNote: function(noteStr) {
model.add({
content: noteStr
});
view.render();
},
getNotes: function() {
return model.getAllNotes().reverse();
},
init: function() {
model.init();
view.init();
},
removeNote: function(){
model.remove();
view.render();
}
};
var view = {
init: function() {
this.noteList = $('#notes');
var newNoteForm = $('#new-note-form');
var newNoteContent = $('#new-note-content');
newNoteForm.submit(function(e){
octopus.addNewNote(newNoteContent.val());
newNoteContent.val('');
e.preventDefault();
});
view.render();
},
render: function(){
var htmlStr = '';
octopus.getNotes().forEach(function(note){
htmlStr += '<li class="note">'+
note.content +
'</li>';
});
this.noteList.html( htmlStr );
},
};
octopus.init();
});
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Udacity Retain</title>
<link rel="stylesheet" href="css/retain.css">
</head>
<body>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="js/retain.js"></script>
<form id="new-note-form" class="new-note-form">
<input id="new-note-content" class="new-note-content">
</form>
<button id = "button">remove!</button>
<ul id="notes" class="notes"></ul>
</body>
</html>

You are nearly there.. I've updated your code to work, see below.
Firstly, you need to make model.remove() actually delete an item by using pop() and then saving the updated data.
Secondly, you need to hook up a click event to the remove button. I've added this in view.init() after where you hook up the form submit.
$(function(){
var model = {
init: function() {
if (!localStorage.notes) {
localStorage.notes = JSON.stringify([]);
}
},
add: function(obj) {
var data = JSON.parse(localStorage.notes);
data.push(obj);
localStorage.notes = JSON.stringify(data);
},
getAllNotes: function() {
return JSON.parse(localStorage.notes);
},
// Updated method below
remove: function() {
var data = JSON.parse(localStorage.notes);
data.pop();
localStorage.notes = JSON.stringify(data);
}
};
var octopus = {
addNewNote: function(noteStr) {
model.add({
content: noteStr
});
view.render();
},
getNotes: function() {
return model.getAllNotes().reverse();
},
init: function() {
model.init();
view.init();
},
removeNote: function(){
model.remove();
view.render();
}
};
var view = {
init: function() {
this.noteList = $('#notes');
var newNoteForm = $('#new-note-form');
var newNoteContent = $('#new-note-content');
var removeBtn = $('#button');
newNoteForm.submit(function(e){
octopus.addNewNote(newNoteContent.val());
newNoteContent.val('');
e.preventDefault();
});
// Added click event on the remove button
removeBtn.on('click', function() {
octopus.removeNote();
});
view.render();
},
render: function(){
var htmlStr = '';
octopus.getNotes().forEach(function(note){
htmlStr += '<li class="note">'+
note.content +
'</li>';
});
this.noteList.html( htmlStr );
},
};
octopus.init();
});
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Udacity Retain</title>
<link rel="stylesheet" href="css/retain.css">
</head>
<body>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="js/retain.js"></script>
<form id="new-note-form" class="new-note-form">
<input id="new-note-content" class="new-note-content">
</form>
<button id = "button">remove!</button>
<ul id="notes" class="notes"></ul>
</body>
</html>

Related

I am having trouble using bind to change the this statement to point to my controller Javascript MVC

I am trying to implement model view controller pattern in a simple print hello world program. I can get everything to work properly except at the end after I click the button in my program. I am trying to bind my function to the controller so that way the function uses the this statements in the controller to access my model and view instances in the controller. When I click the button the this statements in my function are referencing my button object instead of the controller. I am having trouble using bind to change what the this statements point to. Please, any assistance would be greatly appreciated. thank you.
<!DOCTYPE html>
<html lang="en">
<head>
<title> Hello World MVC </title>
<link rel="stylesheet" href="css file name">
</head>
<body>
<div id="container">
<input type=text id="textBox">
<button id="displayButton">Display</button>
</div>
<script src="mainbind.js"></script>
</body>
</html>
function Model(text) {
this.data = text;
};
function View() {
this.displayButton = document.getElementById('displayButton');
this.textBox = document.getElementById('textBox');
this.initialize = function(displayButtonProcess) {
this.displayButton.addEventListener('click', displayButtonProcess);
}
};
function Controller(text) {
this.model = new Model(text);
this.view = new View;
this.buttonClick = function(event) {
// process the button click event
this.view.textBox.value = this.model.data;
};
this.view.initialize(this.buttonClick);
};
let x = new Controller("Hello World");
x.buttonClick = x.buttonClick.bind(x);
The problem is that you are changing controller instance property after you have already used unbinded version as a callback.
You can fix it by binding directly when creating a controller. Or you should better use arrow functions instead.
this.buttonClick = () => this.view.textBox.value = this.model.value
function Model(text) {
this.data = text;
};
function View() {
this.displayButton = document.getElementById('displayButton');
this.textBox = document.getElementById('textBox');
this.initialize = function(displayButtonProcess) {
this.displayButton.addEventListener('click', displayButtonProcess);
}
};
function Controller(text) {
this.model = new Model(text);
this.view = new View;
this.buttonClick = function(event) {
// process the button click event
this.view.textBox.value = this.model.data;
};
this.view.initialize(this.buttonClick.bind(this));
};
let x = new Controller("Hello World");
// x.buttonClick = x.buttonClick.bind(x);
<div id="container">
<input type=text id="textBox">
<button id="displayButton">Display</button>
</div>

Detecting a click after a new element is loaded after the DOM

I am trying to create a simple Chrome Plugin - however I have come to an issue.
I am trying to detect a click on a div using a simple getElementById - however as the api call happens after the DOM is loaded the JS cannot 'find' any div's and gives an error and doesn't do anything after I click on the element.
How do I detect the click, after the data from the API has loaded? I have included some of my code below:
Thanks
document.addEventListener('DOMContentLoaded', function () {
var checkPageButton = document.getElementById('checkPage');
checkPageButton.addEventListener('click', function () {
inputBox = document.getElementById("postcodeInput").value
console.log(inputBox)
let xml = new XMLHttpRequest();
xml.open('get', "https://api.getaddress.io/find/" + inputBox + "/?api-key=SECRET&expand=true", false);
xml.send(null);
var data = xml
var arr = xml.responseText
var data = JSON.parse(arr)
var postcode = data.postcode
var addresses = data.addresses
console.log(addresses)
document.getElementById("postcode").innerHTML = postcode;
var text = "";
var i;
for (i = 0; i < addresses.length; i++) {
text += "<div id='addressClick' name=" + i + ">" + addresses[i].line_1 + "</div>" + "<br>";
}
document.getElementById("data").innerHTML = text;
clickFunc()
}, false);
}, false);
function clickFunc() {
var rowBox = document.getElementById("addressClick");
rowBox.addEventListener('click', function () {
console.log('asd');
}, true);
}
HTML
<!doctype html>
<html>
<head>
<title>Address Search</title>
<script src="popup.js"></script>
</head>
<body>
<h3>Address Search</h3>
<input type="text" id='postcodeInput' name="postcodeInput" value="KW1 4YT">
<button id="checkPage">Search</button>
<div class='results'>
<h3>Results - <span id='postcode'></span></h3>
<p id='data'></p>
</div>
</body>
</html>
<style>
body {
width: 200px
}
#addressClick:hover {
color: blue;
cursor: pointer
}
</style>
You can attach an EventListener to all the body and, at every click, detect if the clicked element is the desired one:
document.body.addEventListener('click', event => window.alert(event.target.innerText));
This can sound like an aggressive solution, but it's way less invasive than a MutationObserver

Ng-Tag-Input Directive not clearing value

I am using ngTagInput directive for auto-suggestion. What I want is clear the auto-suggestion after suggestion is clicked. Though its clearing on first call of function but not on second. It is adding as tag on second function call. and I want to remove that.
On Html,
<tags-input ng-model="autoSongs"
key-property="_id"
display-property="name"
add-from-autocomplete-only="true"
replace-spaces-with-dashes="false"
on-tag-adding="songAdded($tag)"
on-tag-added="emptyScope()"
template="tag-template"
placeholder="type here to search for album..."
on-tag-removed="songRemoved($tag)">
<auto-complete source="loadSongs($query)"
min-length="2"
debounce-delay="5"
max-results-to-show="10"
template="autocomplete-template"></auto-complete>
</tags-input>
This way on controller,
$scope.loadSongs = function(query) {
var autoFilter = 'name=' + query;
return songService.autoSuggest(autoFilter, function(error, data) {
if (error) {
toastr.error(error.message, 'Error');
return false;
}
return data;
});
};
$scope.songAdded = function(query) {
if ($scope.pushArray.checkbox.length === 0) {
toastr.error('Select record to assign album.', 'Error');
return false;
}
var songId = query._id,
songName = query.name;
videoService.assignSong(songId, $scope.pushArray.checkbox, function(err, resonse) {
if (err) {
toastr.error(err.message, 'Error');
} else {
$scope.emptyScope();
toastr.success('\'' + songName + '\' has been assigned to ' + resonse + ' records', 'Success');
$scope.pageChanged();
}
});
return true;
};
$scope.emptyScope = function() {
$scope.autoSongs = null;
};
Its not clearing value on second attempt. Can I use auto-suggestion with callbacks separately?
plunker
If you console log the value of $scope.autoSongs you will find out that it is indeed an array.
So your function that empties the value has to be like this:
$scope.emptyScope = function() {
//$scope.autoSongs = null;
$scope.autoSongs = [];
};
working plunker
PS: please read also this SO answer about emptying an array.
This Plunker seems to work nicely.
on-tag-adding="songAdded($tag)"
Seems to be the only event you need to trigger.
I've tried using $timeout in order to differ emptyScope() function; check if the result is as you want:
var app = angular.module('myApp', ['ngTagsInput']);
app.controller('MainCtrl', function($scope, $timeout, songService) {
$scope.autoSongs = [];
$scope.loadSongs = function(query) {
console.log("loadSongs: " + query);
return songService.autoSuggest(query);
};
$scope.songAdded = function(query) {
console.log("songAdded: " + query);
var songId = query._id,
songName = query.name;
$timeout(function() {
console.log("$timeout: ");
$scope.emptyScope();
});
return true;
};
$scope.emptyScope = function() {
console.log("emptyScope: ");
$scope.autoSongs = [];
};
});
app.factory('songService', function() {
var autoSuggest = function(autoFilter) {
console.log("autoSuggest: " + autoFilter);
if (autoFilter == "i")
return [{name: 'India',_id: 1}, {name: 'Indonesia',_id: 2},{name: 'Italy',_id: 3} ];
else if (autoFilter == "u")
return [{name: 'United Arab',_id: 4}, {name: 'Uganda',_id: 5},{name: 'USA',_id: 6} ];
else
return [{name: 'Germany',_id: 7}, {name: 'Greece',_id: 8},{name: 'Chezk',_id: 9} ];
}
return {autoSuggest:autoSuggest};
});
<!DOCTYPE html>
<html ng-app="myApp">
<head>
<meta charset="utf-8" />
<title>AngularJS Plunker</title>
<script>
document.write('<base href="' + document.location + '" />');
</script>
<!--link rel="stylesheet" href="style.css" /-->
<link rel="stylesheet" href="http://mbenford.github.io/ngTagsInput/css/ng-tags-input.min.css" />
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.2/angular.min.js"></script>
<script src="http://mbenford.github.io/ngTagsInput/js/ng-tags-input.min.js"></script>
<script src="app.js"></script>
</head>
<body ng-controller="MainCtrl">
<tags-input ng-model="autoSongs" key-property="_id" display-property="name" add-from-autocomplete-only="true" replace-spaces-with-dashes="false" on-tag-adding="songAdded($tag)" on-tag-added="emptyScope()" placeholder="type here to search for album..."
on-tag-removed="songRemoved($tag)">
<auto-complete source="loadSongs($query)" min-length="1" debounce-delay="5" max-results-to-show="10"></auto-complete>
</tags-input>
<p>Model: {{autoSongs}}</p>
<p>Search with I or U or else</p>
</body>
</html>
And also the Plunker updated: http://plnkr.co/edit/7nt3toEclsP5HXL7RadP?p=preview

HTML5/webcomponents: Call prototype function from template code

I'm making some tests for using (or not) web components in a single page app I'm creating.
Here's an example for the problem:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
</head>
<body>
<template id="aTemplate">
<div style="border:1px solid red">
<p>text <input type="text"></p>
<button>ClickMe</button>
</div>
</template>
<script>
var Proto = Object.create(HTMLElement.prototype);
Proto.createdCallback = function () {
var t = document.querySelector('#aTemplate');
var clone = document.importNode(t.content, true);
this.createShadowRoot().appendChild(clone);
};
Proto.aFunction = function() {
alert("proto " + "text value?");
}
document.registerElement('x-proto', {prototype: Proto});
var ProtoChild = Object.create(Proto);
ProtoChild.createdCallback = function () {
Proto.createdCallback.call(this)
};
ProtoChild.aFunction = function() {
alert("child " + "text value?");
}
document.registerElement('x-proto-child', {
prototype: Proto
});
</script>
<x-proto></x-proto>
<x-proto-child></x-proto-child>
</body>
The problem is that I cannot find a way to set a "onclick" handler in the button (inside the template) that calls the method "aFunction" in the object created using the prototype. The method should be called in the correct object instance, with access to the internal DOM components, and the attributes and functions in the prototype.
I've tried a lot of things, (binding the event after construction, using JS or JQuery, using the created/attached callbacks, a ) but I'm out of ideas.
Thanks in advance.
EDIT:
Thanks to MinusFour for the answer. The line:
clone.querySelector('button').addEventListener('click', this.aFunction);
in what I was trying to do, anyway, resulted in (same but with JQuery, for testing compatibilitiy):
$(this.showButton).on("click", this.aFunction.bind(this));
The "bind" makes 'this' AKA the container, the complete component, available in JS code, what I needed.
Here's the completed final example, someone may find it helpful:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<script src="http://code.jquery.com/jquery-2.1.4.min.js"></script>
</head>
<body>
<template id="aTemplate">
<div style="border:1px solid darkgray;padding:10px;margin: 10px;">
<h2 class="theCaption"></h2>
<p>text <input class="theText" type="text"></p>
<button class="showButton">Show val</button>
<button class="closeButton">Close</button>
</div>
</template>
<script>
// The .bind method from Prototype.js
if (!Function.prototype.bind) { // check if native implementation available
Function.prototype.bind = function () {
var fn = this, args = Array.prototype.slice.call(arguments),
object = args.shift();
return function () {
return fn.apply(object,
args.concat(Array.prototype.slice.call(arguments)));
};
};
}
function createProto() {
$("#spawnPoint").append("<x-proto x-caption='proto'></x-proto>");
}
function createChild() {
$("#spawnPoint").append("<x-proto-child x-caption='a child of proto'></x-proto-child>");
}
var Proto = Object.create(HTMLElement.prototype);
Object.defineProperty(Proto, "x-caption", {value: "no caption"});
Proto.createdCallback = function () {
var t = document.querySelector('#aTemplate');
var clone = document.importNode(t.content, true);
this.shadowRoot = this.createShadowRoot();
this.shadowRoot.appendChild(clone);
$(clone).children("div").append("<p>ssss</p>")
this.showButton = this.shadowRoot.querySelector('.showButton');
this.closeButton = this.shadowRoot.querySelector('.closeButton');
this.shadowRoot.querySelector('.theCaption').textContent = $(this).attr("x-caption");
this.theText = this.shadowRoot.querySelector('.theText');
$(this.showButton).on("click", this.aFunction.bind(this));
$(this.closeButton).on("click", this.close.bind(this));
};
Proto.aFunction = function () {
alert("in proto = " + $(this.theText).val());
}
Proto.close = function () {
$(this).detach();
}
document.registerElement('x-proto', {prototype: Proto});
var ProtoChild = Object.create(Proto);
ProtoChild.createdCallback = function () {
Proto.createdCallback.call(this);
};
ProtoChild.aFunction = function () {
alert("overrided in child = " + $(this.theText).val());
}
document.registerElement('x-proto-child', {
prototype: ProtoChild
});
</script>
<button onclick="javascript:createProto()">Create proto</button>
<button onclick="javascript:createChild()">Create child</button>
<div id="spawnPoint">
</div>
</body>
I believe you could just add the listener from the importedNode (clone in your case).
clone.querySelector('button').addEventListener('click', function(){
//code logic here
});
You also probably meant:
document.registerElement('x-proto-child', {
prototype: ProtoChild
});
Here's how it would look like:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
</head>
<body>
<template id="aTemplate">
<div style="border:1px solid red">
<p>text <input type="text"></p>
<button>ClickMe</button>
</div>
</template>
<script src="https://cdnjs.cloudflare.com/ajax/libs/webcomponentsjs/0.7.14/webcomponents.min.js"></script>
<script>
var Proto = Object.create(HTMLElement.prototype);
Proto.createdCallback = function () {
var t = document.querySelector('#aTemplate');
var clone = document.importNode(t.content, true);
clone.querySelector('button').addEventListener('click', this.aFunction);
this.createShadowRoot().appendChild(clone);
};
Proto.aFunction = function() {
alert("proto " + "text value?");
}
document.registerElement('x-proto', {prototype: Proto});
var ProtoChild = Object.create(Proto);
ProtoChild.createdCallback = function () {
Proto.createdCallback.call(this);
console.log(this.template);
/*this.template.querySelector('button').addEventListener('click', function(){
console.log('child');
});*/
};
ProtoChild.aFunction = function() {
alert("child " + "text value?");
}
document.registerElement('x-proto-child', {
prototype: ProtoChild
});
</script>
<x-proto></x-proto>
<x-proto-child></x-proto-child>
</body>
I had a similar issue, a while back:
http://www.davevoyles.com/accessing-member-functions-in-polymer/
Are you using Polymer at all, or just Web Components?
As long as you wrap the functions you are trying to call in the polymer-ready event, you should be good to go, and can call functions from your polymer-element.
Polymer parses element definitions and handles their upgrade
asynchronously. If you prematurely fetch the element from the DOM
before it has a chance to upgrade, you’ll be working with a plain
HTMLElement, instead of your custom element.
Alternatively, you can query for a custom element however you want (via ID, class, attr, or element name) and get the same thing. Here’s his example: http://jsbin.com/qikaya/2/edit

jQuery : Using buttons generated by a plugin to interact with

i'm beginner in jQuery Plugin's coding.
I've coded a plugin to generate a Gantt's calendar but i can't succeed with interacting with it.
The code is too long to be posted so i've coded a sample of what a need in kind of interaction.
Here is a sample code that generate a counter and two buttons to increase or decrease the value of the counter.
So the question is : How can i make the button increase / decrease the counter and of course refresh display.
Thanks,
[EDIT]
I explain again what i want to do :
- The buttons are generated by the plugin.
- When they are clicked, they increase/decrease the value and refresh display
- I dont want an external action binding.
- The plugin must be standalone
[/EDIT]
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=iso-8859-2">
<title></title>
<link rel="stylesheet" href="/styles/jquery-ui.css" type="text/css">
<script src="/scripts/jquery.js"></script>
<script src="/scripts/jquery-ui.js"></script>
<script>
(function($){
$.fn.mySample = function() {
var _myCount = 0;
this.initialize = function ()
{
this.html('<input type="button" value="--">Count : ' + _myCount + '<input type="button" value="++">');
}
return this.initialize();
}
})(jQuery);
$(document).ready(
function()
{
$('#myDiv').mySample();
}
);
</script>
</head>
<body>
<div id="myDiv"></div>
</body>
</html>
Sorry for my first answer :)
So here is your mySample plugin working as a standalone
http://jsfiddle.net/P4p3m/2/
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=iso-8859-2">
<title></title>
<script src="jquery-1.11.0.min.js"></script>
<script>
(function($){
$.fn.mySample = function() {
this._counter = 0; // Your counter
this.initialize = function (config)
{
// Get the context of the outside
var context = this;
// Init the -- button
var inputMinus = document.createElement("input");
inputMinus.type = "button";
inputMinus.value = "--";
$(inputMinus).click(function(){
context.updateCounter(-1);
});
this.append(inputMinus);
// Init the display
var spanDisplay = document.createElement("span");
context.spanDisplay = spanDisplay;
$(spanDisplay).text(context._counter);
this.append(spanDisplay);
// Init the ++ button
var inputPlus = document.createElement("input");
inputPlus.type = "button";
inputPlus.value = "++";
$(inputPlus).click(function(){
context.updateCounter(+1);
});
this.append(inputPlus);
}
// Updating the counter value and the display
this.updateCounter = function(nbr){
this._counter += nbr;
$(this.spanDisplay).html(this._counter);
};
// Start the plugin
return this.initialize();
}
})(jQuery);
$(document).ready(
function()
{
$('#myDiv').mySample();
}
);
</script>
</head>
<body>
<div id="myDiv"></div>
</body>
</html>
I modified your code to add id's to better identify the buttons and a span to identify where we want to modify the count:
Here's the updated code to reflect your comments, along with the code working in a fiddle: http://jsfiddle.net/XMgz4/
(function ($) {
$.fn.mySample = function () {
var _myCount = 0;
var inputStr = '<input id="decButton" type="button" value="--">'
+ 'Count : <span id="count">' + _myCount
+ '</span><input id="incButton" type="button" value="++">';
this.html(inputStr);
this.find("#decButton").on("click", function() {
_myCount --;
$("#count").text(_myCount);
});
this.find("#incButton").on("click", function() {
_myCount ++;
$("#count").text(_myCount);
});
}
})(jQuery);
You will need to put the Count : ' + _myCount + ' inside an element.. like this:
'Count : <div id="result">' + _myCount + '</div>'
After that you will create a function to get the value of result.. like this $('#result').html()
and increase or decrease the value using onclick event of your inputs... (You must to add class or id for your inputs...)
I will suppose that the id is btnDecrease and btnIncrease... You will need to do something like this:
$('#btnDecrease').on('click', function(){
$('#result').html(parseInt($('#result').html()) - 1);
});
and
$('#btnIncrease').on('click', function(){
$('#result').html(parseInt($('#result').html()) + 1);
});
Hope it helps

Categories