I am new to knockout.js and am trying to create a simple notification message component. What's happening is that the binding appears to occur, but there are no updates happening to the UI. Below is the code, and I would appreciate any help in locating where this is falling down.
Please note: I am using ASP.NET MVC 5, Knockout.js 3.2.0, Require.js 2.1.14 with AMD for accessing scripts and views.
View - Hub
<div class="row">
<notification-hub></notification-hub>
</div>
<button type="button">Push Me!</button>
#section scripts {
<script src="~/Scripts/Views/Home/index.js" type="text/javascript"></script>
}
Hub Script
require(["ko", "jquery", "ViewModels/notificationMessage", "Components/Notifications/hub", "Plugins/jquery.timeago"], function (ko, jquery, notificationMessage, notificationHub) {
try {
// Register the component.
ko.components.register("notification-hub", {
viewModel: notificationHub,
template: { require: "text!/Components/Notifications/HubItemView" }
});
// Create an instance of the hub and add an inital message.
var hub = new notificationHub();
hub.addMessage(new notificationMessage("Test", "This is a test message.", "2015-02-06 11:00 AM"));
// Bind things up.
ko.applyBindings(hub, $("notification-hub")[0]);
// Create a handler for the button click.
$("button").on("click", function () {
hub.addMessage(new notificationMessage("New Message", "This is a new message", new Date().toLocaleDateString()));
});
}
catch (e) {
$("#displayValues").html("Something went wrong...");
Debug.writeln("Script error: " + e.message);
}
});
ViewModel - Hub
define(["ko", "jquery"], function (ko, jquery) {
// Create the hub's main ViewModel.
function notificationHub() {
var self = this;
// Define the Properties.
self.messages = ko.observableArray();
self.count = ko.computed(function () { return self.messages().length; });
}
// Define the addMessage method.
notificationHub.prototype.addMessage = function (msg) {
var self = this;
// Pop message to the top of the stack.
self.messages().push(msg);
Debug.writeln("Count of message array: " + self.messages().length);
}
return notificationHub;
});
View - Message Model
<p data-bind="if: messages().length == 0">Unfortunately we didn't find any records.</p>
<ul data-bind="foreach: messages">
<li class="notificationMessage">
<span class="timeAgo" data-bind="text: createdDate"></span>
<h2 data-bind="text: title"></h2>
<p data-bind="text: message"></p>
</li>
</ul>
<!-- For debugging purposes -->
<input type="text" data-bind="value: count" />
ViewModel - Message Model
define(["ko"], function (ko) {
// Define the ViewModel for the messages themselves.
return function notificationMessage(title, message, date) {
var self = this;
// Define the Properties.
self.title = title;
self.message = message;
self.createdDate = date;
};
});
Related
I took over this legacy .NET webforms application, where KO has been used extensively, and I am trying to get an understanding of KO bindings.
The setup:
- a master page containing:
- a usercontrol Announcements.ascx
- a reference to a javascript file named after which page it has been inserted to, lets call it helppageinit.js
The helppageinit.js contains code like this:
$(function () {
var basketViewModel = new AjaxBasket();
var quickBuy = new QuickBuyViewModel(basketViewModel);
$.when(basketViewModel.OnLoad, quickBuy.OnLoad).always(function () {
LeftBasketView(basketViewModel);
ko.applyBindings({ BasketViewModel: basketViewModel, QuickBuyViewModel: quickBuy });
});
});
I want to add KO code to a new file called AnnouncementsInit.js, which is referenced on the master page. The contents of AnnouncementsInit.js is:
$(window).load(function () {
var announcementsViewModel = new AnnouncementsViewModel();
$.when(announcementsViewModel.OnLoad).always(function () {
ko.applyBindings(announcementsViewModel, document.getElementById('announcementContainer'));
});
});
ko.bindingHandlers.stopBinding = {
init: function () {
return { controlsDescendantBindings: true };
}
};
AnnouncementsViewModel looks like this:
AnnouncementsViewModel = function (languange) {
var _onLoad = $.Deferred();
var _announcements = ko.observableArray();
var _singleAnnouncement = ko.observable();
var _imageWidth = ko.observable("40px");
var onClose = function () {
...code removed...
};
_onLoad.resolve();
return {
Announcements: _announcements,
SingleAnnouncement: _singleAnnouncement,
ImageWidth: _imageWidth,
OnClose: onClose,
OnLoad: _onLoad.promise()
};
};
Announements.ascx contains this markup:
<div data-bind="stopBinding: true">
<div id="announcementContainer">
<div class="popContainer" style="display: none">
<!-- ko if: SingleAnnouncement -->
...markup here
<!-- /ko -->
</div>
<div class="divModal" style="display: none" data-cookiename="<%= CookieName %>">
<!-- ko if: Announcements -->
...code here...
<!-- /ko -->
</div>
</div>
</div>
The problem:
The problem is that stopBinding does not seem to work properly. 'Sometimes', I get this error in the console:
Uncaught Error: Unable to parse bindings.
Message: ReferenceError: Announcements is not defined;
Bindings value: if: Announcements
I guess the error occurs because applyBindings in helppageInit.js, applies bindings after announcementInit.js has called applyBindings, even though stopBinding should hinder that.
Any help is much appreciated...
I have some code I am trying to reverse engineer. Here is the script...
define(function (require) {
"use strict";
var $ = require('jquery'),
Handlebars = require('handlebars'),
groupAdapter = require('adapters/group'),
homeHtml = require('text!tpl/Home.html'),
groupListItemHtml = require('text!tpl/GroupList.html'),
homeTpl = Handlebars.compile(homeHtml),
groupListItemTpl = Handlebars.compile(groupListItemHtml);
return function () {
this.initialize = function () {
// Define a div wrapper for the view. The div wrapper is used to attach events.
this.$el = $('<div/>');
this.$el.on('keyup', '.search-key', this.findByName);
};
this.render = function () {
this.$el.html(homeTpl());
return this;
};
this.findByName = function () {
groupAdapter.findByName($('.search-key').val()).done(function (products) {
$('.product-list').html(groupListItemTpl(products));
});
};
this.initialize();
};
});
...here is the template that is being used in tpl/home.html
<div class="topcoat-navigation-bar">
<div class="topcoat-navigation-bar__item center full">
<h1 class="topcoat-navigation-bar__title">Daily Comedy App</h1>
</div>
</div>
<div class="search-bar">
<input type="search" placeholder="search" class="topcoat-search-input search-key">
</div>
<div class="topcoat-list__container scroller" style="top:138px;">
<ul class='topcoat-list list product-list '></ul>
</div>
So...the current functionality is that a search bar is displayed on the home page, and when text is typed data from my json is displayed on the page.
What I would like to do is simply loadd all the data from my json on this initial page, rather than have the user search.
My JS stinks, so be kind! ;)
PS phonegap people might recognise this code from the EU Day.
I use Web Api and Knockout.js in my project. I want to try like this: if I click the "Home" I want to refresh just main div. So I write this code.
My script in layout.cshtml
<script type="text/javascript">
$(document).ready(function () {
ko.applyBindings(new TalesViewModel());//First load the code is runnig and load the main div
function TalesViewModel() {
var self = this;
self.tales = ko.observableArray();
$.getJSON("/api/tales/", self.tales);
}
$('#home').click(function () {
var Tale = function (TaleName, Content, VoicePath, Tales) {
self = this;
self.TaleName = TaleName;
self.Content = Content;
self.VoicePath = VoicePath;
}
var mapping = {
'tales': {
create: function (options) {
return new Tale(options.data.TaleName, options.data.Content,
options.data.VoicePath);
}
}
}
var data = $.getJSON("/api/tales/", Tale);
var viewModel = ko.mapping.fromjs(data, mapping);
ko.applyBindings(viewModel);
})
})
</script>
I want to refresh this place
<div id="main">
#RenderBody()
</div>
TaleList.cshtml (PartialView)
<div>
<ul data-bind="foreach: tales">
<li>
<div>
<div>Masal Adı</div>
<span data-bind="text: $data.TaleName"></span>
</div>
<div>
<div>İçerik</div>
<span data-bind="text: $data.Content"></span>
</div>
<div>
<div>Ses Dosyası</div>
<span data-bind="text: $data.VoicePath"></span>
</div>
</li>
</ul>
When I clicked Home main div is refresh but no data in here. I think I have to use Knockout something but I don't know how can I do it.
I hope I can explain. Thanks all replies.
Update
If I check with firebug I see this error "TypeError: Object # has no method 'fromjs'"
Update2
I added my first knockout code when I load the project.
This is what you need to do:
Create a js object
var Tale = function (TaleName, Content, VoicePath, Tales) {
self = this;
self.TaleName = TaleName;
self.Content = Content;
self.VoicePath = VoicePath;
}
Create a mapping to convert to your js objects
var mapping = {
'tales': {
create: function(options) {
return new Tale(options.data.TaleName, options.data.Content,
options.data.VoicePath);
}
}
}
Check that your data matches something like below, checking the names match as below:
var data = {"tales" : [{"TaleName": "T1", "Content":"c1", "VoicePath":"v1"}, {"TaleName": "T2", "Content":"c2", "VoicePath":"v2"}]}
var viewModel = ko.mapping.fromJS(data, mapping);
Apply the bindings
ko.applyBindings(viewModel);
Here is a working fiddle with mimicked data
http://jsfiddle.net/dxJpc/1/
Update
You are mixing a combination of getJson and ajax, you only need one.
This can be replaced: (With Ajax)
$.ajax({
type: 'GET',
url: '/Pages/TaleList/',
contentType: 'application/html; charset=utf-8',
dataType: 'html'
})
.success(function (data) {
alert("okey!")
var viewModel = ko.mapping.fromJS(data, mapping);
ko.applyBindings(viewModel);
})
.error(function (req, status, error) {
alert("Error!Occured")
})
With getJSON:
var data = $.getJSON("/api/tales/", Tale);
var viewModel = ko.mapping.fromJS(data, mapping);
ko.applyBindings(viewModel);
Update 3
If you are loading your initial load as you have changed it to, you can simply put this in your on click event:
$('#home').click(function () {
ko.applyBindings(new TalesViewModel());
})
Update 4
Declare the view model in the document ready.
$(document).ready(function () {
var viewModel = new TalesViewModel();
ko.applyBindings(viewModel);
Then change your click to this:
$(document).ready(function () {
viewModel = new TalesViewModel();
my first post on stackoverflow. (and english is not my native tongue).
I am trying to learn how to use emberjs.
It's not easy because of the lack of good tutorials.
So I decided to code a chat, I use nodejs and socket.io server-side.
Html
<script type="text/x-handlebars">
<div class="page">
<div class="users">
</div>
<div class="messagebox">
{{#view App.TextField valueBinding="currentMsg" placeholder="your message"}}{{/view}}
<button {{action "sendMsg"}}>Send</button>
</div>
<div id="chatbox">
{{#collection contentBinding="App.MsgsController" tagName="ul"}}
<b>{{value}}</b>
{{/collection}}
</div>
</div>
</script>
Javascript
var id;
var socketio = io.connect("127.0.0.1:8888");
socketio.on('id', function (data) {
id = data;
});
socketio.on("broadcast", function (data) {
if (id == data.id) {
return
}
App.MsgsController.addMsg(data.message);
});
socketio.on("own", function (data) {
App.MsgsController.addMsg(data.message);
});
App = Ember.Application.create();
App.Msg = Ember.Object.extend({
value: null
});
App.MsgsController = Ember.ArrayController.create({
content: [],
addMsg: function (value) {
var msg = App.Msg.create({
value: value
});
this.pushObject(msg);
}
});
App.TextField = Ember.TextField.extend({
insertNewline: function() {
this.get("controller").send("sendMsg");
}
});
App.ApplicationController = Ember.Controller.extend({
currentMsg: 't',
sendMsg: function () {
var currentMsg = this.get('currentMsg');
if(currentMsg) {
socketio.emit("message", { message: currentMsg, id: id});
this.set('currentMsg', '');
}
}
});
I want to focus App.TextField after the App.ApplicationController.sendMsg call.
I tried
App.TextField.$().focus()
but it seems that I can only use $() inside of its methods.
Someone can help me, please?
Edit :
Ok, I found the answer.
App.TextField is like "class", and the one on the view is an instance.
I must add an id in my view
{{#view App.TextField valueBinding="currentMsg" placeholder="your message" id="mytextfield"}}{{/view}}
and use jquery selector to access to the instance
$('#mytextfield').focus();
Use didInsertElement hook for the view to handle jquery methods.
http://emberjs.com/api/classes/Ember.View.html#event_didInsertElement
I just start using Backbone. I have two questions to ask based on the code below.
The first issue is after I fill out the form and click the button, the model object should be created with some default attributes. However, the console.log prints the model with the newest attribute from the form I fill out before I pass the model to the new view object.
The second issue is I can successfully save the data to db, but my success call back function is not being called. could someone help me to answer these questions??
var form = document.forms[0];
var RetailerModel = Backbone.Model.extend({
urlRoot: ' retailer.php',
defaults: {
name: 'company-name',
address: 'company-address',
phone: 'company-phone',
icon: 'http://localhost/icon.png'
}
});
var RetailerCollection = Backbone.Collection.extend({
});
var RetailerView = Backbone.View.extend({
className: 'retailer',
template: _.template($('#retailer-template').html()),
initialize: function() {
//this.listenTo(this.model, 'change', this.render);
var obj = {
name: form.name.value,
address: form.address.value,
phone: form.phone.value
};
this.model.set(obj);
//why the successful callback does not work????
this.model.save(null, {success: function(model, response){console.log('successful');}});
},
render: function() {
$('#retailer-list').append(this.$el.html(this.template(this.model.toJSON())));
return this;
}
});
var RetailerViews = Backbone.View.extend({
});
$('#submit').click(function(e){
var retailer_model = new RetailerModel();
console.log(retailer_model); // this prints out the new changed attributes instead of the default ones, why???
var retailer_view = new RetailerView({model: retailer_model});
form.reset();
});
1 Get the correct state of the model
console.log(retailer_model) will not show the models attributes, it'll show the whole model, but console.log(retailer_model.attributes) will. Also bear in mind that console.log isn't always on correct, especially if you modify the object just after you log it this can lead to confusion!
To get the actual current state of the model you should make a shallow or deep copy of it. So rather than console.log(model) you could use underscores clone method for a shallow copy:
console.log(_(model).clone());
2 Success Callback on Save
To help there we'd really need to know more about your circumstances. The first thing I'd look for is whether your server is returning the right feedback. Backbone is a system based on REST standards. Are you sure your server is returning the right response code? If you use chrome, open your developer tools and then the network tab to inspect what response you're getting when posting your model. In order for the success callback to be fired the server should return a status of 200 or 201.
Another way to test that is to see whether the error callback is firing :).
I tried to see your errors, I realized that you missed these things:
1. You should do DOM related things when DOM ready event fired:
$(function() {
form = document.forms[0];
$(form).live('submit', function(e){
// ...
});
});
2. You can use submit for forms. It catches also enter click within form:
$(form).live('submit',function(){
//...
});
3. You should use return false; within submit form. It prevents default form data sending to action url.
$(form).live('submit', function(e){
// ...
return false;
});
So, it looks like this. I didn't check success callback, but I hope it will work.
<html>
<head>
<script src="jquery-1.7.2.js" type="text/javascript"></script>
<script type="text/javascript" src="underscore.js"></script>
<script type="text/javascript" src="backbone.js"></script>
<script type="text/javascript">
var RetailerModel = Backbone.Model.extend({
urlRoot: ' retailer.php',
defaults: {
name: 'company-name',
address: 'company-address',
phone: 'company-phone',
icon: 'http://localhost/icon.png'
}
});
var RetailerCollection = Backbone.Collection.extend({});
var RetailerView = Backbone.View.extend({
className: 'retailer',
//template: _.template($('#retailer-template').html()),
initialize: function() {
//this.listenTo(this.model, 'change', this.render);
var obj = {
name: form.name.value,
address: form.address.value,
phone: form.phone.value
};
this.model.set(obj);
//why the successful callback does not work????
this.model.save(null, {
success: function(model, response){
console.log('successful');
}
});
},
render: function() {
$('#retailer-list').append(this.$el.html(this.template(this.model.toJSON())));
return this;
}
});
var RetailerViews = Backbone.View.extend({});
$(function() { // you should do DOM related things when DOM ready event fired
form = document.forms[0];
$(form).live('submit', function(e){
var retailer_model = new RetailerModel();
console.log(retailer_model);
var retailer_view = new RetailerView({model: retailer_model});
form.reset();
return false; // to prevent form data sending return false
});
});
</script>
</head>
<body>
<form action="#" id="#submit">
<input type="submit" value="submit"/>
<input type="text" name="address"/>
<input type="text" name="name"/>
<input type="text" name="phone"/>
</form>
</body>
</html>