I have my own knockout's component:
ko.components.register("library-link-form",
{
viewmodel: LibraryLinkViewModel,
template: { controller: "PartialViews", action: "LibraryLinkPartial" }
//This is custom template loader, which loads asp.net partial view from controller via ajax request.
});
My LibraryLinkViewModel.js:
function LibraryLinkViewModel() {
var self = this;
self.OtherLibrary = ko.observable("");
self.Type = ko.observable("");
}
Partial view _LibraryLinkForm:
#{
var libraryDropdownId = $"dropdown-{Guid.NewGuid().ToString().Substring(0, 8)}";
var typeDropdownId = $"dropdown-{Guid.NewGuid().ToString().Substring(0, 8)}";
var scriptId = $"script-{Guid.NewGuid().ToString().Substring(0, 8)}";
var contextId = $"context-{Guid.NewGuid().ToString().Substring(0, 8)}";
var librariesList = //some list with predefined libraries
var typeList = // some list with predefined library's types
}
<!-- ko template: { afterRender: function()
{
eval($('##scriptId').html());
}
}
-->
<!-- /ko -->
<div id ="#contextId">
<div class="row">
<div class="col-md-12">
<div class="panel panel-default">
<div class="panel-body">
<div class="col-md-12">
<div class="row">
<form class="form-horizontal">
<div class="form-group">
<div class="col-md-6">
#(Html.Kendo().DropDownList()
.Name(libraryDropdownId)
.DataValueField("Value").DataTextField("Text")
.HtmlAttributes(new
{
style = "width: 100%",
data_bind = "value: OtherLibrary"
}).BindTo(librariesList).Deferred()
)
</div>
<div class="col-md-4">
#(Html.Kendo().DropDownList()
.Name(typeDropdownId)
.DataValueField("Value").DataTextField("Text")
.HtmlAttributes(new
{
style = "width: 100%",
data_bind = "value: Type"
}).BindTo(typeList).Deferred()
)
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
<deferred-script class="hidden" id="#scriptId">
#(Html.Kendo().DeferredScripts(false))
</deferred-script>
</div>
And finally, how I combine it all:
<button type="button" data-bind="click: addLibraryLink"></button>
<ul class="list-unstyled" data-bind="foreach: LibraryLinks">
<li><library-link-form></library-link-form></li>
</ul>
<script type="text/javascript">
function LibraryViewModel() {
var self = this;
self.LibraryLinks = ko.observableArray();
self.addLibraryLink = function () {
ko.components.clearCachedDefinition();
self.LibraryLinks.push(new LibraryLinkViewModel());
};
}
ko.applyBindings(new LibraryViewModel());
</script>
I'm using Knockout v.3.4, Asp.Net Core v.1.0.0.
So, the problem is that when I'm trying to add new library link to list, knockout bindings simply don't work, maybe because of error:
Uncaught ReferenceError: Unable to process binding "value: function
(){return OtherLibrary }" Message: OtherLibrary is not defined
What should I do with this error? How can I properly add my knockout's component to the list?
The answer is simple. Let's look at the example, and check contexts in it:
<div data-bind="foreach: LibraryLinks"> // here we have LibraryViewModel context
<library-link-form> // here we have LibraryLinkViewModel context
//inside component we have THIRD context, which is empty!
</library-link-form>
</div>
So, the problem is, that OtherLibrary in data_bind = "value: OtherLibrary" refers to the third context, which is empty and nowhere defined.
Simply calling parent's context solves the problem.
For example: data_bind = "value: $parent.OtherLibrary"
Related
I'm a bit new to knockout. I'm trying to get a custom component to dynamically load another custom component. I have a variable called location_board that contains html and that html has a custom component in it. . When I use the data-bind="html: location_board" it put the line for the in the dom but it doesn't run the custom component to fill out that node. Note: If I add the npc-widget directly to the template it works. It just doesn't work when it is added though the html binding. From my research I think this means I need to applyBindings on it? I'm not sure how to go about that in this situation though.
Any help is apricated.
Here is my full code for the custom component.
import {Database} from './database.js'
let database = new Database();
import {ResourceManager} from "./resource-manager.js";
let resourceManager = new ResourceManager();
let locationRegister = {
fog_forest: {
name: "The Ghostly Woodland",
image: "url('img/foggy_forest.jpeg')",
description: `
Place holder
`,
location_board: `
<npc-widget id="john-npc" params="id: 1, tree: 'shopkeep', speed: 50"></npc-widget>
<div>In</div>
`
}
};
ko.components.register('location-widget', {
viewModel: function (params) {
let self = this;
self.function = function () {
console.log("Functions!")
}
for(let k in locationRegister[params.id]) {
console.log(k)
this[k] = locationRegister[params.id][k];
}
console.log(this.name)
//return { controlsDescendantBindings: true };
},
template:
`
<div class="row">
<div class="col-lg-12">
<h2 id="title" class="tm-welcome-text" data-bind="html: name"></h2>
</div>
</div>
<div class="row">
<div class="col-lg-12">
<div data-bind="style: { 'background-image': image}" class="location-picture mx-auto d-block">
</div>
</div>
</div>
<div class="row">
<div class="col-lg-12">
<div class="location-description mx-auto d-block" data-bind="html: description"></div>
</div>
</div>
</div>
<hr>
<div class="row">
<div class="col-lg-12">
<div id="location_board" data-bind="html: location_board">
</div>
</div>
</div>
`
});
I want to create a simple weather report website using Vue.js, I just learned this framework and had accessed public data before. But this time I am stuck.
There are two versions of methods I have tried to get data.
var app = new Vue({
el: "#weather",
data: {
city: '',
weather:[],
date: new Date().toDateString(),
greeting:''
},
methods: {
//method 1
getData: function () {
var city = this.city
$.getJSON("http://api.openweathermap.org/data/2.5/weather?q=" + city + "&units=metric&lang=en&appid=a495404234abce9b5830b1e8d20e90a6",
function (data) {
console.log(data)
});
},
//method 2
getData: function () {
$("#search").keypress(function (e) {
if (e.which == 13) {
var city = $("#search").val();
if (city != " ") {
var url = "http://api.openweathermap.org/data/2.5/weather?q=" + city + "&units=metric&lang=en&appid=a495404234abce9b5830b1e8d20e90a6";
console.log(url);
}
$.getJSON(url, function (data) {
this.weather = data.weather;
console.log(data);
this.returnGreeting();
})
}
})
},
}
}
});
<div class="container" id="weather">
<h1>Weather Pro</h1>
<div class="float-left"><h2>{{date}}</h2></div>
<div class="float-right" ><h3 id="time"></h3></div>
<p>{{greeting}}</p>
<div class="input-group">
<form>
<input v-model="city" class='searchbar transparent' id='search' type='text' placeholder=' enter city' />
<input id='button' #click="getData" type="button" value='GO' />
</form>
</div>
{{city}}
<div class="panel">
<div class="panel-body" v-for="d in data">
<p>
{{data}}
</p>
</div>
<ul class="list-group list-group-flush">
<!-- <li class="list-group-item">{{data.weather[0].main}}</li>
<li class="list-group-item">{{data.weather[0].description}}</li> -->
</ul>
</div>
</div>
<!-- vue -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<!-- jQuery library -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
I got an error :
[Vue warn]: Property or method "data" is not defined on the instance but referenced during render. Make sure that this property is reactive, either in the data option, or for class-based components, by initializing the property. See: https://v2.vuejs.org/v2/guide/reactivity.html#Declaring-Reactive-Properties.
Consider data to be your model. Don't reference data directly in your view, reference properties that are on the model instead.
So instead of <div>{{data.city}}</div> use <div>{{city}}</div>
var app = new Vue({
el: "#weather",
data() {
return {
city: '',
weather: [],
date: new Date().toDateString(),
greeting: ''
};
},
methods: {
getData() {
fetch("http://api.openweathermap.org/data/2.5/weather?q=" + this.city + "&units=metric&lang=en&appid=a495404234abce9b5830b1e8d20e90a6")
.then(res => res.json())
.then(data => {
this.weather = data.weather;
});
}
}
});
<div class="container" id="weather">
<h1>Weather Pro</h1>
<div class="float-left">
<h2>{{date}}</h2>
</div>
<div class="float-right">
<h3 id="time"></h3>
</div>
<p>{{greeting}}</p>
<div class="input-group">
<form>
<input v-model="city" class='searchbar transparent' id='search' type='text' placeholder=' enter city' />
<input id='button' #click="getData" type="button" value='GO' />
</form>
</div>
{{city}}
<div class="panel">
<div class="panel-body" v-for="d in weather">
<p>
{{d}}
</p>
</div>
<ul class="list-group list-group-flush"></ul>
</div>
</div>
<!-- vue -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.min.js"></script>
<!-- jQuery library -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
I found out what caused the issues:
I need to define data in data, as I reference data directly in my html page, but this is optional.
Turns out there is a slim jQuery version from bootstrap that overrides the min jQuery. And $.getJSON() needs min jQuery.
looks like zero beat me to it, but here's a version using jquery call
the issue is, as mentioned in comment, that data.data is not defined. so define data inside data, and assign result to this.data. However, because it's inside a function and the scope changes, you need to store scope using var that = this and use that.data = data to assign result
dom:
<div class="container" id="weather">
<h1>Weather Pro</h1>
<div class="float-left"><h2>{{date}}</h2></div>
<div class="float-right" ><h3 id="time"></h3></div>
<p>{{greeting}}</p>
<div class="input-group">
<form>
<input v-model="city" class='searchbar transparent' id='search' type='text' placeholder=' enter city' />
<input id='button' #click="getData" type="button" value='GO' />
</form>
</div>
{{city}}
<div class="panel">
<div class="panel-body" v-for="d in data">
<p>
{{d}}
</p>
</div>
<ul class="list-group list-group-flush">
</ul>
</div>
</div>
<!-- jQuery library -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
Script:
var app = new Vue({
el: "#weather",
data: {
city: '',
weather:[],
date: new Date().toDateString(),
greeting:'',
data: null,
},
methods: {
//method 1
getData: function () {
var that = this;
var city = this.city
console.log('getData')
$.getJSON("https://api.openweathermap.org/data/2.5/weather?q=" + city + "&units=metric&lang=en&appid=a495404234abce9b5830b1e8d20e90a6",
function (data) {
console.log(data)
that.data = data;
});
},
}
});
Here is an example fiddle.
I have googled around, and tried to fix this as good as i can with examples i have found around, but alas... no success.
Mission :
Modal is opened and displaying checkbox for selecting an already existing user
If clicked -> Dropdown visible with available persons to select from
Source of dropdown (select) works as it should..
When person is selected from dropdown, a api-call (not implemented yet) will return an object to fill newOrExistingPlayer observable, and displaying it's data in fields..
If no person selected from dropdown, it's a new registration without pre-selecting a person.
Error :
knockout-3.4.0.debug.js:3326 Uncaught ReferenceError: Unable to process binding "with: function (){return newOrExistingPlayer }"
Message: Unable to process binding "value: function (){return selectedPersonId }"
Message: selectedPersonId is not defined
Problem :
Before a person is selected, newOrExistingPlayer is "undefined". Therefore i made a "teamPlayerDefault" js-object with the data similar to what should be returned from the api call (not implemented yet).
This is for initializing..
I don't think i'm handling empty observables the correct way. Should they be initialized in some way to avoid this ?
JSFiddle Link :
Click here...
Code :
$(document).ready(function() {
var NewTeamPlayerViewModel = function() {
var teamPlayerDefault = {
Id: 0,
ExistingPersonId: 0,
Email: "",
Email2: "",
FirstName: "",
LastName: "",
Address: "",
PostalCode: "",
PostalCity: "",
Phone: "",
Phone2: "",
BirthdayString: "",
ShirtNo: 0,
TeamIdString: getQueryVariable("teamId")
};
var self = this;
self.existingPersonChecked = ko.observable(false);
self.existingPersons = ko.observableArray();
self.selectedPersonId = ko.observable(null);
self.selectedPersonId.subscribe(function(selPersonId) {
// Handle a change here, e.g. update something on the server with Ajax.
console.log('Valgt personid ' + selPersonId);
});
self.newOrExistingPlayer = ko.observable(teamPlayerDefault);
self.setExistingPlayer = function(personId) {
// TODO : GET EXISTING PLAYER
self.newOrExistingPlayer(null);
console.log(self.newOrExistingPlayer());
}
self.toggleExistingPersonChecked = function() {
self.existingPersonChecked(!self.existingPersonChecked);
}
// TODO UGLE : Ikke hent alle personer, men ekskluder de som allerede er spillere på laget!!!
self.initializeFromServer = function() {
//var teamId = getQueryVariable("teamId");
var url = 'api/User/GetAllPersons';
$.getJSON(url)
.done(function(data) {
newPlayerModel.existingPersons(data);
//console.table(data);
});
}
}
var newPlayerModel = new NewTeamPlayerViewModel();
newPlayerModel.initializeFromServer();
ko.applyBindings(newPlayerModel, document.getElementById("ko-player"));
console.log("Heisann!" + newPlayerModel.newOrExistingPlayer());
});
<div id="ko-player">
<div class="modal fade" data-bind="with: newOrExistingPlayer" id="full-modal-player" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true" style="z-index: 999999999999">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
<h4 id="myModalLabel">Ny spiller</h4>
</div>
<div class="modal-body" style="height: 100% !important; max-width: 100%; height:800px">
<div class="row">
<div class="col-sm-12">
<div class="col-sm-6">
<div class="checkbox">
<label class="checkbox-label">Velg eksisterende person?</label>
<input type="checkbox" data-bind="checked: $parent.existingPersonChecked, click: $parent.toggleExistingPersonChecked" />
</div>
</div>
<div class="col-sm-6" style="display: none" data-bind="visible: $parent.existingPersonChecked">
<div class="form-group">
<label>Velg person:</label>
<select data-bind="options: $parent.existingPersons, value: selectedPersonId, optionsCaption: 'Velg en person'"></select>
</div>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Lukk</button>
<div class="clear:both; height:1px"> </div>
</div>
</div>
</div>
</div>
</div>
You likely have figured this out by now but I was able to get it binding with -
<select data-bind="value: $parent.selectedPersonId"></select>
Just to expand on why this is, you are binding 'with' newOrExistingPlayer and need to step up a level to access where you defined the selectedPersonId
I'm building a wizard widget with Durandal, and I'd like to use it like so:
<div data-bind="wizard: options">
<!-- Step 1 -->
<span data-part="step-header-1">
Step 1
</span>
<div data-part="step-content-1">
step content here
</div>
<!-- Step 2 -->
<span data-part="step-header-2">
Step 2
</span>
<div data-part="step-content-2">
step content here
</div>
</div>
This is the actual widget (cut down for brevity):
<div class="wizard-container">
<ul class="steps" data-bind="foreach: steps">
<li>
<span data-bind="html: heading"></span>
</li>
</ul>
<!-- ko foreach: steps -->
<div class="wizard-step" data-bind="css: { active: isActive }">
<div data-bind="html: content">
</div>
</div>
<!-- /ko -->
</div>
I've sort of gotten it working, using jQuery to grab the data-parts, assign the data-part's inner HTML to a property on my step model, and then use the html-binding to bind the content to each step. This works on the DOM side of things, but doing it this way means that my step content won't get data-bound.. I am pretty sure it's because I use the html binding, which does not bind the content.
Is there a way to do this with Durandal widgets, without separating each step into a new view?
Here's an implementation that uses a traditional Durandal master/detail approach in combination with a Tab widget. The tab widget only implements the tabbing functionality, while the Master controls what's pushed into it and the Detail controls the behavior/layout of itself.
Master
Viewmodel
define(['./tab', 'plugins/widget', 'knockout'], function (Tab, widget, ko) {
return {
tabs: ko.observableArray([
new Tab('Durandal', 'A ...', true),
new Tab('UnityDatabinding', 'A ...'),
new Tab('Caliburn.Micro', 'C ...')
]),
addNewTab: function() {
this.tabs.push(new Tab('New Tab ', 'A test tab.'));
}
};
});
View
<div>
<h1>Tabs sample</h1>
<!-- ko widget : {kind: 'tabs', items : tabs} -->
<!-- /ko -->
<button class="btn" data-bind="click: addNewTab">Add</button>
</div>
Detail
Viewmodel
define(['durandal/events', 'knockout'], function(events, ko) {
return function(name, content, isActive) {
this.isActive = ko.observable(isActive || false);
this.name = name;
this.content = content;
};
});
view
<div>
<div data-bind="html: description"></div>
</div>
Tab widget
Viewmodel
define(['durandal/composition', 'jquery'], function(composition, $) {
var ctor = function() { };
ctor.prototype.activate = function(settings) {
this.settings = settings;
};
ctor.prototype.detached = function() {
console.log('bootstrap/widget/viewmodel: detached', arguments, this);
};
ctor.prototype.toggle = function(model, event){
this.deactivateAll();
model.isActive(true);
};
ctor.prototype.deactivateAll = function(){
$.each(this.settings.items(), function(idx, tab){
tab.isActive(false);
});
};
return ctor;
});
View
<div class="tabs">
<ul class="nav nav-tabs" data-bind="foreach: { data: settings.items }">
<li data-bind="css: {active: isActive}">
<a data-bind="text: name, click: $parent.toggle.bind($parent)"></a>
</li>
</ul>
<div class="tab-content" data-bind="foreach: { data: settings.items}">
<div class="tab-pane" data-bind="html: content, css: {active: isActive}"></div>
</div>
</div>
Live version available at: http://dfiddle.github.io/dFiddle-2.0/#extras/default. Feel free to fork.
As I suspected, the problem with my bindings not applying, was due to the fact that I used the html binding to set the step content. When Knockout sets the HTML, it does not apply bindings to it.
I wrote my own HTML binding handler, that wraps the HTML and inserts it as a DOM-node - Knockout will hapily apply bindings to this.
(function(window, $, ko) {
var setHtml = function (element, valueAccessor) {
var $elem = $(element);
var unwrapped = ko.utils.unwrapObservable(valueAccessor());
var $content = $(unwrapped);
$elem.children().remove().end().append($content);
};
ko.bindingHandlers.htmlAsDom = {
init: setHtml,
update: setHtml
};
}(window, jQuery, ko));
Please note, this only works when the binding value is wrapped as a node - e.g within a div tag. If not, it won't render it.
I am trying to create a modal view and have a base class that all modals need and then extending it for more specific functionality.
PlanSource.Modal = Ember.View.extend({
isShowing: false,
hide: function() {
this.set("isShowing", false);
},
close: function() {
this.set("isShowing", false);
},
show: function() {
this.set("isShowing", true);
}
});
PlanSource.AddJobModal = PlanSource.Modal.extend({
templateName: "modals/add_job",
createJob: function() {
var container = $("#new-job-name"),
name = container.val();
if (!name || name == "") return;
var job = PlanSource.Job.createRecord({
"name": name
});
job.save();
container.val("");
this.send("hide");
}
});
I render it with
{{view PlanSource.AddJobModal}}
And have the view template
<a class="button button-green" {{action show target=view}}>+ Add Job</a>
{{#if view.isShowing}}
<div class="modal-wrapper">
<div class="overlay"></div>
<div class="dialog box box-border">
<div class="header">
<p class="title">Enter a job name.</p>
</div>
<div class="body">
<p>Enter a name for your new job.</p>
<input type="text" id="new-job-name" placeholder="Job name">
</div>
<div class="footer">
<div class="buttons">
<a class="button button-blue" {{action createJob target=view}} >Create</a>
<a class="button" {{action close target=view}}>No</a>
</div>
</div>
</div>
</div>
{{/if}}
The problem is that when I click the button on the modal dialog, it gives me an "action createJob" can not be found. Am I extending the objects incorrectly because it works if I put the createJob in the base Modal class.
Fixed
There was an issue somewhere else in my code. The name got copied and so it was redefining it and making the method not exist.