I'm begin transform part of system in my work in Knockout Component, but any time, I need access functions defined in my component.
Example I will create a msgComponent:
In Html:
<body>
<msgComponent params="message: 'blablabla'"></msgComponent>
</body>
In Js:
ko.components.register('msgComponent', {
viewModel: function (params)
{
this.message = ko.observable(params.message);
this.showMessage = function()
{
alert(this.messenge());
}.bind(this);
},
template: '<div data-bind="text: message"></div>'
});
function myMasterViewModel()
{
var master = this;
// How to can I access my component this way?
master.msgComponent = msgComponent;
master.showMessage();
}
Like in my example, how to can I access my component function with other view model?
Fist Edit:
#Dandy
If I need instance more msgComponent in page, I can define this way?
var sharedComponent = function(params){
var params = params || {};
this.message = ko.observable(params.message);
this.showMessage = function()
{
alert(this.message());
}.bind(this);
}
ko.components.register('msgcomponent', {
viewModel: function(params)
{
this.instance = params.viewModel;
},
template: '<div data-bind="with: instance"><div data-bind="text: message"></div></div>'
});
function myMasterViewModel()
{
var master = this;
master.msg = new sharedComponent({message: 'huahua'});
master.show = function()
{
master.msg.showMessage();
};
}
Or have any more correct way to this situation?
You can try the instance method of component registration. Something like
var sharedComponent = function(params){
var params = params || {};
this.message = ko.observable(params.message);
this.showMessage = function()
{
alert(this.message());
}.bind(this);
}
var comShared = new sharedComponent ({message: 'test mssage'});
ko.components.register('msgcomponent', {
viewModel: {instance: comShared },
template: '<div data-bind="text: message"></div>'
});
function myMasterViewModel()
{
var master = this;
// How to can I access my component this way?
master.msgComponent = comShared;
master.msgComponent.showMessage();
}
Read more here.
Related
at the company where Im at we use jquery and a lot of the code is very spaghetti haphazard code. So in an effort to organize it better im researching implementing the pub sub model described in this article
So I made a really basic version of it like so:
var topics = {};
jQuery.Topic = function( id ) {
var callbacks, method,
topic = id && topics[ id ];
if ( !topic ) {
callbacks = jQuery.Callbacks();
topic = {
publish: callbacks.fire,
subscribe: callbacks.add,
unsubscribe: callbacks.remove
};
if ( id ) {
topics[ id ] = topic;
}
}
return topic;
};
$(function() {
var testService = new TestService();
testService.subscribe();
var testView = new TestView(testService);
testView.initEvents();
});
/* ---------------------VIEW----------------- */
var TestView = function(testService) {
this.testService = testService;
};
TestView.prototype.initEvents = function () {
this.publishers();
};
TestView.prototype.publishers = function() {
$("#search").on("click", function () {
var isValid = this.testService.validateForm("#container");
if(isValid){
$.Topic( "search" ).publish();
}
})
};
/* ---------------------SERVICE----------------- */
var TestService = function() {
this.testIdea = [];
};
TestService.prototype.validateForm = function (section) {
var referralValid = true;
$(section).find('input,select').filter('[required]:visible').each(function (i, requiredField) {
if(requiredField.value === '') {
//'breaks' the loop out
referralValid = false;
return referralValid;
}
});
return referralValid;
};
TestService.prototype.search = function() {
};
TestService.prototype.subscribe = function() {
var self = this;
$.Topic("search").subscribe( function() {
self.search()
});
};
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<div>
<div id="container">
<input type="text">
</div>
<button id="search">Search</button>
</div>
However when I put that in jsfiddle I get the error that Uncaught TypeError: TestService is not a constructor
in the stackoverflow snippet and on my local version I get a different error of Uncaught TypeError: Cannot read property 'validateForm' of undefined. I cant see what Im doing wrong. Any pointers?
You can declare constructor functions in the way you are doing it (assigning constructor to variable):
var TestView = function(testService) {
this.testService = testService;
};
Like in this simple example:
var myClass = function(name) {
this.name = name;
}
myClass.prototype = {
hello: function() {
console.log('Hello ' + this.name);
}
}
var me = new myClass('Andrew');
me.hello();
But you must remember to declare them before they are used. If you use function statement(https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function) as suggested by Chad Watkins it helps only because of hoisting(http://adripofjavascript.com/blog/drips/variable-and-function-hoisting.html) not because of function statement being mandatory for constructors.
The error in your code is in line:
$("#search").on("click", function () {
var isValid = this.testService.validateForm("#container");
you are referencing jQuery object inside a callback not TestView instance, you probably wanted something like this(pun not intended):
...
var self = this;
$("#search").on("click", function () {
var isValid = self.testService.validateForm("#container");
...
I'm trying to render a partial view within a Backbone View with it's render method. I created a sort of helper to do this.
var DashboardPartial = (function(){
var _getPartialView = function() {
$.ajax({
url: _baseUrl + _url,
})
.done(function(response) {
_returnView(response);
})
.fail(function() {
console.log("error");
})
.always(function() {
console.log("complete");
});
};
var _returnView = function (response) {
return response;
};
return {
get: function (url) {
_url = url;
_baseUrl = '/dashboard/';
_getPartialView();
},
};
}());
So, what I want to do is call DashboardPartial.get('url') and use the response within the Backbones View render method. Something like the following:
render: function() {
partial = DashboardPartial.get('url');
this.$el.html(partial);
return this;
}
The problem is that the function does get the partial from the server, but I can't find a way to return the response. Doing console.log(response) inside the DashboardPartial function does show the partial, but I want to be able to return it and then pass it as a variable to "this.$el.html()".
You should return deferred ($.ajax returns it by default) from helper:
var DashboardPartial = (function(){
var _getPartialView = function() {
return $.ajax({
url: _baseUrl + _url,
});
};
return {
get: function (url) {
_url = url;
_baseUrl = '/dashboard/';
return _getPartialView();
},
};
}());
And then use it in your render:
render: function() {
var self = this,
dfd = $.Deferred();
DashboardPartial.get('url').done(function(partial){
self.$el.html(partial);
dfd.resolve();
});
return dfd; // use this outside, to know when view is rendered; view.render().done(function() { /* do stuff with rendered view */});
}
However, you could use requirejs for this, plus requirejs-text plugin to load templates, because your view has dependency on partial.
As I understood, you want render different partials using one backbone view.
You could create factory, smth like this:
var typeToTemplateMap = {};
typeToTemplateMap["backboneViewWithFirstPartial"] = firstPartial;
function(type) {
return typeToTemplateMap[type];
}
And then use it in your view:
initialize: function(options) {
this.partial = partialFactory(options.type);
},
render: function() {
this.$el.html(this.partial);
return this;
}
This is how it would look like using requirejs:
// factory.js
define(function(require) {
var partialOne = require("text!path/to/partialOne.htm"), // it's just html files
partialTwo = require("text!path/to/partialTwo.htm");
var typeToPartialMap = {};
typeToPartialMap["viewWithFirstPartial"] = partialOne;
typeToPartialMap["viewWithSecondartial"] = partialTwo;
return function(type) {
return typeToPartialMap[type];
}
});
// view.js
define(function(require){
var Backbone = require("backbone"), // this paths are configured via requirejs.config file.
partialFactory = require("path/to/factory");
return Backbone.View.extend({
initialize: function(options) {
this.partial = partialFactory(options.type);
},
render: function() {
this.$el.html(this.partial);
return this;
}
});
});
// Another_module.js, could be another backbone view.
define(function(require) {
var MyView = require("path/to/MyView"),
$ = require("jquery");
var myView = new MyView({type: "firstPartial"});
myView.render(); // or render to body $("body").append(myView.render().$el);
});
You should consider using requirejs, because you doing almost the same, but without dependencies handling.
Docs on requirejs can be found here: http://requirejs.org/docs/start.html
So I bind my Knockout template as follows:
First ajax, get data then I pass the data can call a function named bindKo:
function bindKo(data) {
var length = data.length;
var insertRecord = {};
if (length > 0) {
insertRecord = data[data.length - 1]; //last record is an empty PremlimViewModel for insert
insertRecord.Add = true;
data.splice(data.length - 1, 1); //remove that blank insert record
}
function prelims(data) {
var self = this;
var model = ko.mapping.fromJS(data, { copy: ["_destroy"] }, self);
self.BidPriceFormatted = ko.computed({
read: function () {
var bidPrice = this.BidPrice();
if (bidPrice) {
if (!isNaN(bidPrice)) {
var input = '<input type="text" value="' + bidPrice + '"/>';
return $(input).currency({ decimals: 0 }).val();
}
}
},
write: function (value) {
value = value.replace(/\D/g, '');
this.BidPrice(value);
},
owner: this
});
return model;
}
var mapping = {
create: function (options) {
return new prelims(options.data);
}
};
function viewModel(prelimData) {
var self = this;
self.prelims = ko.mapping.fromJS(prelimData, mapping);
self.remove = function (prelim) {
self.prelims.destroy(prelim);
};
self.addOption = function () {
var clone = jQuery.extend(true, {}, insertRecord);
self.prelims.push(ko.mapping.fromJS(clone));
};
}
ViewModel = new viewModel(data);
ko.applyBindings(ViewModel);
}
I have a template defined where you can add and remove records, and user does just that:
<script type="text/html" id="PrelimsTemplate">
<!--Template Goodness-->
</script>
Then, ajax call, records updated in datanbase, latest results returned and I do:
ko.mapping.fromJS(newestData, ViewModel)
But this does not work because my ViewModel is complex.
So I would just like to reBind the template entirely. Make is disappear and reappear with latest data.
Wrap your template in a container than you can hook onto with jQuery.
When you need to trash it use ko.cleanNode and jQuery .empty()
emptyTemplate: function(){
ko.cleanNode($('#template-container')[0]);
$('#template-container').empty();
}
Load your template back up
fillTemplate: function(){
$('#template-container').html('<div data-bind="template: {name:\'templateId\', data: $data}"></div>');
ko.applyBindings(data,$('#template-container')[0])
},
See my fiddle
I am trying to implement a baseEntity class for entities such as user and groups but isFavourite property can not read the correct Id() from persistanceId() when it is in baseEntity. (type and Id() come out as undefined and surprisingly type has the correct value in confirmDelete)
define(["knockout"], function (ko) {
var app = require('durandal/app');
ko.baseEntity = function (data) {
var self = this;
self.Id = ko.observable();
self.confirmDelete = function () {
var result;
app.showMessage(
'Are you sure you want to delete the ' + self.type + ' ' + self.Name() + '?',
'Deleting ' + self.type, ['Yes', 'No']).then(
function (dialogResult) {
dialogResult === "Yes" ? result = true : result = false;
});
return result;
};
self.persistanceId = function () {
return self.type + '-' + self.Id() + "-IsFavourite";
};
self.isFavourite = ko.observable(false).extend({
persist: self.persistanceId()
});
self.toggleFavourite = function () {
self.isFavourite(!self.isFavourite());
};
}
return {
model: ko.baseEntity
}
});
but if isFavourite instead of being here in baseEntity it is for example part of group then it works fine.
define(["knockout", "models/baseentity"], function (ko, baseEntity) {
var
model = function (data) {
var self = this;
baseEntity.model.call(self);
self.type = 'group';
self.Id(data.Id);
self.Name = ko.observable(data.Name);
self.Description = ko.observable(data.Description);
self.Members = ko.observableArray(data.Members);
self.isFavourite = ko.observable(false).extend({
persist: self.persistanceId()
});
}
return {
model: model
}
});
Could someone explain to me what is going on here and how can I move my base property back in my baseentity as it is shared across various other things too.
I am no javascript master but i would look to decouple the inheritance of your model hierarchy from knockout - does the model behave as expected if you implement it vanilla?
I don't follow why you would want to modify the knockout object itself? I believe Dave Lowe is correct in suggesting that you do this in JavaScript alone. Properties on your model, to the extent that they affect your view, should be observable, but your model doesn't need to be attached to knockout.
Also, consider spending some time at http://objectplayground.com, which has a great tutorial on learning object oriented JavaScript. Your paradigm should look a little more like this:
function Model(obj) {
this.attribute = obj.attribute;
this.observable = ko.observable(obj.observable);
this.localFunction = function(val) {
if (obj.attr == true) this.observable(val);
};
}
Model.prototype.globalFunction = function(data) {
this.observable(data);
};
Notice, in particular, that if the method depends on the local variable, that is, the argument passed to the constructor, then it needs to be defined in the constructor. Otherwise, you should define methods on the prototype.
This paradigm works excellently with require in durandal since you can do the following:
define(function(require) {
var Model = require('model');
var object = new Model({});
})
OK, apparently the easier way to do this subClass business in ko is using ko itself. Who would have thought :)
So now I have defined my base model to be:
define(["knockout"], function (ko) {
var app = require('durandal/app');
ko.baseEntity = function (type, data) {
var self = this;
self.Id = ko.observable(data.Id);
self.Type = ko.observable(type);
self.Name = ko.observable();
self.persistanceId = ko.computed(function () {
return self.Type() + '-' + self.Id() + "-IsFavourite";
});
self.isFavourite = ko.observable(false).extend({
persist: self.persistanceId()
});
self.toggleFavourite = function () {
self.isFavourite(!self.isFavourite());
};
self.confirmDelete = function () {
var result;
app.showMessage('Are you sure you want to delete the ' + self.Type() + ' ' + self.Name() + '?', 'Deleting ' + self.Type(), ['Yes', 'No'])
.then(function (dialogResult) {
dialogResult === "Yes" ? result = true : result = false;
});
return result;
};
}
return {
model: ko.baseEntity
}
});
As you can see I have now added the same data parameter that I pass into my concrete implementation. This is possible as later I will use ko.utils.extend to create an instance of this and extend it:
define(["knockout", "models/baseentity", "config"], function (ko, baseEntity, config) {
var
model = function (data) {
var self = this;
ko.utils.extend(self, new baseEntity.model(config.subClassTypes.user, data));
self.Id(data.Id);
self.FirstName = ko.observable(data.FirstName);
self.LastName = ko.observable(data.LastName);
self.JobTitle = ko.observable(data.JobTitle);
self.UserLevel = ko.observable(data.UserLevel);
self.Groups = ko.observableArray(data.Groups);
self.ImageUrl = data.ImageUrl;
self.Name(self.FirstName() + ' ' + self.LastName());
}
return {
model: model
}
});
another subclass example:
define(["knockout", "models/baseentity", "config"], function (ko, baseEntity, config) {
var
model = function (data) {
var self = this;
ko.utils.extend(self, new baseEntity.model(config.subClassTypes.group, data));
self.Id(data.Id);
self.Name(data.Name);
self.Description = ko.observable(data.Description);
self.Members = ko.observableArray(data.Members);
}
return {
model: model
}
});
This way I have managed to get my methods transferred over to the base and it works fine. I hate answering my own questions, so waiting for someone to add some nice valuable answers I can tick.
For example,
I have the page where KO has been already registered and there is a viewmodel with observable property "someProperty";
I check that the "someProperty" is observable property by ko.isObservable(viewmodel.someProperty) - it returns 'true';
I do the ajax call to get some html markup where KO is registered too;
Now If you check the ko.isObservable(viewmodel.someProperty) it will return false;
Also all KO extensions which has been added manually will be lost. It looks like bug (or feature) in jQuery (http://bugs.jquery.com/ticket/10066).
var viewModel = new function() {
var self = this;
this.serverData = {
Controller: ko.observable(null),
Enabled: ko.observable(false),
Id: ko.observable(null),
ParentId: ko.observable(null),
Title: ko.observable(null),
MaterialId: ko.observable(null),
Alias: ko.observable(null)
};
this.treeData = {
tree: ko.observable(null),
node: ko.observable(null)
};
this.submit = submit;
this.cancel = cancel;
this.openMaterials = menuOptions.openMaterials;
}
// ...
var data = ko.utils.createUnobservable(viewModel.serverData);
// ...
(function(ko) {
ko.utils = ko.utils || {};
ko.utils.createUnobservable = function(observable) {
var unobservable = {};
(function() {
for (var propertyName in observable) {
var observableProperty = observable[propertyName];
if (ko.isObservable(observableProperty) /* always 'false' after ajax */) {
unobservable[propertyName] = observableProperty();
}
}
})();
return unobservable;
};
})(ko = ko || {});
You could fix this by saving a copy of the ko global variable before you include the loaded ajax, and then restoring it afterwards.
var savedKo = window.ko;
.... // do the ajax thing
window.ko = savedKo;