I am trying to make a pure js mvc app where I update an h1 with the text of an input field. I got to the point that the the value of the input in the model can be logged nicely but for some reason the h1 is not changing at all.
Could you give me some help that why is that and how to solve it?
my code:
window.onload = function() {
var model = new Model();
var controller = new Controller(model);
var view = new View(controller);
};
function Model() {
this.inputtext = "zzzzz";
this.heading = this.inputtext;
console.log('model called');
};
function Controller(model) {
var controller = this;
this.model = model;
this.handleEvent = function(e) {
switch (e.type) {
case "click":
controller.clickHandler(e.target);
break;
case "input":
controller.keyupHandler(e.target);
break;
default:
console.log(e.target);
}
}
this.getModelHeading = function() {
console.log("from getmodel: " + controller.model.inputtext + "heading " + controller.model.heading);
return controller.model.heading;
}
this.keyupHandler = function(target) {
controller.model.inputtext = target.value;
controller.getModelHeading();
}
console.log('controller called');
};
function View(controller) {
this.controller = controller;
this.heading = document.getElementById("heading");
this.heading.innerHTML = controller.getModelHeading();
this.inputtext = document.getElementById("inputtext");
this.inputtext.addEventListener('input', controller);
console.log('view called');
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" : content="width=device-width">
<title>Vanilla MVC Framework</title>
<script type="text/javascript" src="./Config.js"></script>
<script type="text/javascript" src="./Model.js"></script>
<script type="text/javascript" src="./Controller.js"></script>
<script type="text/javascript" src="./View.js"></script>
</head>
<body>
<input id='inputtext' /></input>
<h1 id='heading'></h1>
</body>
</html>
You need to link the view to the controller, then modify the view from the controller.
window.onload = function() {
var model = new Model();
var controller = new Controller(model);
var view = new View(controller);
};
function Model() {
this.inputtext = "zzzzz";
this.heading = this.inputtext;
console.log('model called');
};
function Controller(model) {
var controller = this;
this.model = model;
this.handleEvent = function(e) {
switch (e.type) {
case "click":
controller.clickHandler(e.target);
break;
case "input":
controller.keyupHandler(e.target);
break;
default:
console.log(e.target);
}
}
this.getModelHeading = function() {
// console.log("from getmodel: " + controller.model.inputtext + "heading " + controller.model.heading);
controller.model.heading = controller.model.inputtext;
return controller.model.heading;
}
this.keyupHandler = function(target) {
controller.model.inputtext = target.value;
controller.view.heading.innerHTML=controller.getModelHeading();
}
console.log('controller called');
};
function View(controller) {
this.controller = controller;
this.heading = document.getElementById("heading");
this.heading.innerHTML = controller.getModelHeading();
this.inputtext = document.getElementById("inputtext");
this.inputtext.addEventListener('input', controller);
controller.view = this;
console.log('view called');
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" : content="width=device-width">
<title>Vanilla MVC Framework</title>
<script type="text/javascript" src="./Config.js"></script>
<script type="text/javascript" src="./Model.js"></script>
<script type="text/javascript" src="./Controller.js"></script>
<script type="text/javascript" src="./View.js"></script>
</head>
<body>
<input id='inputtext' />
<h1 id='heading'></h1>
</body>
</html>
You update the h1 element only in the constructor of View class.
In keyUp event handler you update the only model but you haven't reassigned the view.heading.innerHtml value.
Only your View should know about where in DOM to display a model.property. Therefore, my suggestion to you add this code in your View:
function View(controller) {
var _self = this;
this.controller = controller;
this.heading = document.getElementById("heading");
updateHeading.call(_self);
this.inputtext = document.getElementById("inputtext");
this.inputtext.addEventListener('input', function(e){
controler.handleEvent(e);
updateHeading.call(_self);
});
console.log('view called');
function updateHeading(){
this.heading.innerHTML = controller.getModelHeading();
}
}
Related
i like to pass to a function pointer to function that the addEventListener will use .
if you run this you will get an error .
what is the "Javascript" way to pass a function pointer ( don't know how to call it ) to addEventListener ?
<!DOCTYPE html>
<html lang="en">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta charset="UTF-8">
<title>Title</title>
<style>
</style>
<script>
var data = {
"foo1" : "aaa",
"foo2" : "bbb",
"foo3" : "ccc"
}
var createLabel = function(mykey,func) {
var label = document.createElement('label');
label.innerHTML = mykey;
label.id = "lbl_"+mykey;
label.addEventListener("click", () =>{
self.func(mykey);
}, false);
document.getElementById("container2").appendChild(label);
var br = document.createElement('br');
document.getElementById("container2").appendChild(br);
};
var popolateDS = function() {
self = this;
var i = 0;
for(var key in data) {
(function () {
var mykey = key;
if (data.hasOwnProperty(key)) {
if(i==0) {
createLabel(key,dsOnClick1);
i++;
}
createLabel(key,dsOnClick2);
}
}()); // immediate invocation
}
}
var dsOnClick1 = function(key) {
alert("dsOnClick1 "+key);
}
var dsOnClick2 = function(key) {
alert("dsOnClick2 "+key);
}
</script>
</head>
<body>
<div id="container2">
</div>
<button onclick="popolateDS()">click</button>
</body>
</html>
You don't need to refer to this as self.func, you can just call func like below and it'll work as expected:
label.addEventListener("click", () =>{
func(mykey);
}, false);
In the following code I get an error: TypeError: i.Print is not a function when the button is clicked. What is the cause of this error, and how do I fix it? Using Firefox debugger when I look at the value of i in the button's click handler, I see that i.prototype.Print has value Outer/Inner.prototype.Print().
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
</head>
<body>
<p id="prn">Value here</p>
<button id='btn'>Print</button>
<script src="https://code.jquery.com/jquery-1.12.4.min.js">
</script>
<script>
function TestObject(i)
{
$("#btn").on("click", function() {
i.Print();
i.Change(Math.random() * 10 + 5);
});
}
function TestPrototype()
{
var o = function Outer() {
function Inner(v)
{
var iv = v;
function print()
{
$("#prn").text(iv);
}
};
Inner.prototype.Print = function() {
print();
console.log(iv);
};
Inner.prototype.Change = function(nv) {
iv = nv;
};
return {
getInner : function(v) {
var i = Inner;
i(v);
return i;
}
};
}();
var i1 = o.getInner(10);
TestObject(i1);
}
;(function() {
TestPrototype();
}());
</script>
</body>
</html>
You need to create an object using the constructor,
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
</head>
<body>
<p id="prn">Value here</p>
<button id='btn'>Print</button>
<script src="https://code.jquery.com/jquery-1.12.4.min.js">
</script>
<script>
function TestObject(i)
{
$("#btn").on("click", function() {
i.Print();
i.Change(Math.random() * 10 + 5);
});
}
function TestPrototype()
{
var o = function Outer() {
function Inner(v)
{
// instatiate member variables
this.iv = v;
this.print = print;
function print()
{
$("#prn").text(this.iv);
}
};
Inner.prototype.Print = function() {
// access member variable
console.log(this.iv);
this.print();
print();
};
Inner.prototype.Change = function(nv){
iv = nv;
};
return {
getInner : function(v) {
var i = Inner;
return new i(v);
}
};
}();
var i1 = o.getInner(10);
TestObject(i1);
}
;(function() {
TestPrototype();
}());
</script>
</body>
</html>
Calling a function from a button is easy in javascript:
b.onclick = f;
Calling a method from a button can also be done:
Myclass.prototype.m = function() {
var t = this;
b.onclick = t.f;
}
But we want to call a method of our class from a button through another function. Is there any way to do this without passing in the original object?
Here is the code that does not work. this.e is interpreted in the context of the button.
<!DOCTYPE html>
<html>
<body>
</body>
<script>
function A() {}
A.prototype.e = function() {
console.log("bar");
}
A.prototype.f = function() {
console.log("foo!");
this.e();
}
A.prototype.g = function() {
var b = document.createElement("button");
b.innerHTML = "say foo!";
b.onclick = this.f;
document.getElementsByTagName("body")[0].appendChild(b);
}
var a = new A();
a.g();
</script>
</html>
Use Function.prototype.bind to change the context of the this keyword to your a instance
<!DOCTYPE html>
<html>
<body>
</body>
<script>
function A() {}
A.prototype.e = function() {
console.log("bar");
}
A.prototype.f = function() {
console.log("foo!");
this.e();
}
A.prototype.g = function() {
var b = document.createElement("button");
b.innerHTML = "say foo!";
b.onclick = this.f.bind(this);
document.getElementsByTagName("body")[0].appendChild(b);
}
var a = new A();
a.g();
</script>
</html>
Since the flow interrupted you should bind the following
<!DOCTYPE html>
<html>
<body>
</body>
<script>
function A() {}
A.prototype.e = function() {
console.log("bar");
}
A.prototype.f = function() {
console.log("foo!");
this.e();
}
A.prototype.g = function() {
var b = document.createElement("button");
b.innerHTML = "say foo!";
b.onclick = this.f.bind(this)
document.getElementsByTagName("body")[0].appendChild(b);
}
var a = new A();
a.g();
</script>
</html>
I have built an AngularJS project before and am familiar with the syntax. This time around my ng-controller="UniversalCtrl as universal" is not working and I have tried everything. If I take universal.showHeader == true and change it to showHeader == true it works, but I need it to be the variable of universal. Like I said I have other projects following this same structure and they work fine.
Here is my HTML code:
<!DOCTYPE html>
<html>
<head lang="en">
<meta http-equiv="cache-control" content="no-store" />
<meta http-equiv="expires" content="0" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta http-equiv="pragma" content="no-store" />
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=yes">
<link href="styles/MarkStrap.css" rel="stylesheet" />
<link href="styles/Site.css" rel="stylesheet" />
<script type="text/javascript" src="js/Angular/angular.min.js"></script>
<script type="text/javascript" src="js/Angular/angular-route.min.js"></script>
<script type="text/javascript" src="js/Universal/Universal.js"></script>
<script type="text/javascript" src="js/Universal/Filters.js"></script>
<script type="text/javascript" src="js/Universal/Directives.js"></script>
<title>WIN</title>
<link rel="shortcut icon" href="winIcon.ico">
</head>
<body ng-app="winApp" ng-controller="UniversalCtrl as universal">
<index-header ng-show="universal.showHeader == true"></index-header>
<ng-view></ng-view>
<script type="text/javascript" src="js/Applications/Applications.js"></script>
</body>
</html>
Here is my Universal.js setup:
(function () {
var winSystem = angular.module('winApp', ['ngRoute']);
winSystem.config(function ($sceProvider, $routeProvider) {
$routeProvider
.when('/Applications', {
templateUrl: "view-app.html",
controller: "AppController"
})
.otherwise({
templateUrl: "404.html"
})
});
winSystem.service("sharedData", function () {
var reloadData = false;
var beginAppLoad = false;
var reloadNotes = false;
self.httpGet = function (http, url, callback) {
http.get(baseUrl + url, {
headers: { "Session-Id": localStorage.ogSessionId }
}).success(function (data) {
callback(data);
}).error(function (data, status, headers, config) {
if (status == "401") {
localStorage.removeItem("ogSessionId");
localStorage.removeItem("ogUserId");
window.location.href = loginUrl;
}
});
};
self.httpPost = function (http, url, content, callback) {
http.post(baseUrl + url, {
Content: JSON.stringify(content)
}, {
headers: {
"Session-Id": localStorage.ogSessionId
}
})
.success(function (data) {
callback(data);
})
.error(function (data, status, headers, config) {
if (status == "401") {
localStorage.removeItem("ogSessionId");
localStorage.removeItem("ogUserId");
window.location.href = loginUrl;
}
});
};
});
winSystem.controller("UniversalCtrl", ['$scope', '$http', 'sharedData', function ($scope, $http, sharedData) {
var self = $scope;
self.sharedDataSvc = sharedData;
self.isLocal = false;
if (location.href.indexOf("localhost") > -1) {
self.isLocal = true;
} else self.isLocal = false;
self.account = {};
self.actions = [];
self.notifications = [];
self.alertCount = 0;
self.showAlert = false;
self.showHeader = true;
self.alertMessage = "";
}]);
})();
You are binding the models to the scope object instead of the controller instance.
Try:
winSystem.controller("UniversalCtrl", ['$http', 'sharedData', function ($http, sharedData) {
var self = this;
self.sharedDataSvc = sharedData;
self.isLocal = false;
if (location.href.indexOf("localhost") > -1) {
self.isLocal = true;
} else self.isLocal = false;
self.account = {};
self.actions = [];
self.notifications = [];
self.alertCount = 0;
self.showAlert = false;
self.showHeader = true;
self.alertMessage = "";
}]);
And i noticed that you are using self variable on the service sharedData but you haven't initialized it. Just the same 'var self = this;'
I have a list of elements. When I click an up or down icon, I would like the list to rearrange itself and, finally, for the app to rerender itself so I can see the change reflected in the DOM.
Changing list position works. I'm running into issues when I try to run the refreshState method. I'm passing the function as a property of the child but calling that property returns undefined function.
Q: How do I call a component's method from its child component?
Code:
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<title>Test</title>
<script src="http://fb.me/react-with-addons-0.12.2.js"></script>
<script src="http://fb.me/JSXTransformer-0.12.2.js"></script>
</head>
<body>
<style>
/* CSS */
span {
margin:0 0 0 10px;
}
</style>
<div id="app"></div>
<script type="text/jsx">
// React
var _data = ['Red', 'Blue', 'Green'];
function getState() {
return {
colors: _data,
}
};
Array.prototype.swap = function(a, b) {
var temp = this[a];
this[a] = this[b];
this[b] = temp;
};
var App = React.createClass({
getInitialState: function() {
return getState();
},
render: function() {
var colors = this.state.colors.map(function(color) {
return (
<Color name={color} refreshState={this.refreshState} />
)
});
return (
<ul>{colors}</ul>
)
},
refreshState: function() {
return this.setState(getState());
},
});
var Color = React.createClass({
moveUp: function() {
var current = _data.indexOf(this.props.name),
above = current - 1;
if (above >= 0) {
_data.swap(current, above);
}
return this.props.refreshState();
},
moveDown: function() {
var current = _data.indexOf(this.props.name),
below = current + 1;
if (below < _data.length) {
_data.swap(current, below);
}
return this.props.refreshState();
},
render: function() {
return (
<li>
<strong>{this.props.name}</strong>
<span onClick={this.moveUp}>^</span>
<span onClick={this.moveDown}>v</span>
</li>
)
},
});
React.render(<App />, document.getElementById('app'));
</script>
</body>
</html>
Noticed you have solved the question, but thought I'd mention that you can pass a scope to .map which means no need to cache scope for the purpose you describe:
this.state.colors.map(function(color) {
return (
<Color
key={_data.indexOf(color)}
name={color}
refresh={this.refreshState}
/>
)
}, this); // pass the scope to .map
I was trying to call this.refreshState within the map method. This, of course, does not have the same scope as the render method. The solution was to store the scope in a variable:
var refresh = this.refreshState;
Then use that variable within the map method:
... refreshState={refresh} ...
Always be aware of your scope!
If you have multiple functions that aren't within the local scope then you can store this in a variable.
var self = this;
z.map(function(arg) {
x={self.refreshState} y={self.otherThing}
And for the curious, the finished result:
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<title>Test</title>
<script src="http://fb.me/react-with-addons-0.12.2.js"></script>
<script src="http://fb.me/JSXTransformer-0.12.2.js"></script>
</head>
<body>
<style>
/* CSS */
span {
margin:0 0 0 10px;
}
</style>
<div id="app"></div>
<script type="text/jsx">
// React
var _data = ['Red', 'Blue', 'Green'];
function getState() {
return {
colors: _data,
}
};
Array.prototype.swap = function(a, b) {
var temp = this[a];
this[a] = this[b];
this[b] = temp;
};
var App = React.createClass({
getInitialState: function() {
return getState();
},
refreshState: function() {
return this.setState(getState());
},
render: function() {
var self = this;
var colors = this.state.colors.map(function(color) {
return (
<Color
key={_data.indexOf(color)}
name={color}
refresh={self.refreshState}
/>
)
});
return (
<ul>{colors}</ul>
)
},
});
var Color = React.createClass({
propTypes: {
name: React.PropTypes.string,
refresh: React.PropTypes.func.isRequired,
},
moveUp: function() {
var current = _data.indexOf(this.props.name),
above = current - 1;
if (above >= 0) {
_data.swap(current, above);
}
return this.props.refresh();
},
moveDown: function() {
var current = _data.indexOf(this.props.name),
below = current + 1;
if (below < _data.length) {
_data.swap(current, below);
}
return this.props.refresh();
},
render: function() {
return (
<li>
<strong>{this.props.name}</strong>
<span onClick={this.moveUp}>^</span>
<span onClick={this.moveDown}>v</span>
</li>
)
},
});
React.render(<App />, document.getElementById('app'));
</script>
</body>
</html>