Is it possible to use Knockout ONLY for viewing/using other objects of custom class?
I'm trying to find a way to open knockout with different data but always the same structure.
What I did:
// I have an Event class which looks like that:
function cEvent(id){
this.id = id;
}
// I keep an array of instances of that class in something like:
var arr = [new cEvent(1), new cEvent(2)]
On the html page I have:
Event ID: <span data-bind="text: id"></span>
I created an accessor-like class to get data from a specific event with Knockout
cEvent2 = function cEvent2(baseEvent) {
this.id = ko.computed(function(){
return baseEvent.id;
});
}
When I use ko.applyBindings(arr[0]); it works but what if I want to load another "model" without cleaning nodes and reapplying knockout on the page?
What I want:
I'd like to have something like ko.applyBindings(arr[1]); that would update the interface based on the data I want.
Of course in reality the cEvent class is much more complex, but I am trying to see if we're able to get something done without directly extending these instances of cEvent with knockout.
Maybe I'm just trying to do something wrong and it's not the way knockout want to work? I know that in my case I want knockout to serve as a "simple class reader" even if it could do more.
Any tip would be really appreciated.
Here's what I would do:
function cEvent(id){
this.id = id;
}
function myViewModel() {
var arr = [new cEvent(1), new cEvent(2)]
this.selectedEvent = ko.observable(arr[0]);
}
ko.applyBindings(new myViewModel());
That way, if you bind to selectedEvent.id all you need to do when you want to view a different event is update the selectedEvent property and all of your bindings will be automatically updated.
Related
I am new to Knockoutjs and JavaScript, and need some advice.
In my HTML (View) page, I am trying to show a text property from my Javascript object (Model), which looks something like:
var object = function() {
this.text = "blah blah blah";
}
In my Object's ViewModel, I have this:
var objectViewModel= function (object) {
var content = ko.observable(object); // or is it object.text() ?
self.section = function() {
return content.text; //or is it content ?
}
}
And in my view, I have this:
<span data-bind="text:section"></span>
My main question is how do I make the HTML show a model's property (the text) via viewmodel? I commented in my other questions, and would like some help.
Thanks in advance!
So I'd recommend this post as a good read.
To answer both of the additional commented questions: It all depends on what you passed as the parameter. That important bit of information would be provided when you instantiate your viewmodel, which you left out.
As specified, you'll need to ko.applyBindings(new objectViewModel(new object())).
Secondly, you have self, where did it come from? Make it this or declare var self = this; or provide the rest of the code from which that variable is coming from.
Next, from within the section function you need to "read" your content observable:
return content().text
Finally, in your view you need to execute section:
<span data-bind="text:section()"></span>
As an additional consideration, you could make section a computed observable with a dependency on content.
this.section = ko.computed(function() {
return content().text;
})
which removes the need to execute in the view. Check out this fiddle with two different scenarios depicted.
Assuming I have something like:
var MyApp = function() {
this.name = "Stacy"
}
MyApp.prototype.updateName = function(newname) {
this.name = newname;
}
In my main page I have a :
$(function () {
var instance = new MyApp();
})
I have a button event handler that would update the name:
$("#button").on("click", function(evt) {
// Update the name in MyApp to something else...
instance.name = "john" // I do not like using instance here, because it has to be "instance" has to be created before I can use it. I want to be able to make this independent of "instance" being created or not
});
What is the proper way to do it such that the button handler would update "MyApp" to have the correct name, without explicitly using the created "instance" of myapp as part of the button's click handler?
ideally I would like to shove that jquery event handler somewhere into "MyApp" such that I could do something like:
MyApp.prototype.events = function() {
$("#button").on("click", function(evt) {
this.name = "john"
});
}
Though it doesnt work because this refers to something else.
How to properly structure my application such that the event handler is more or less updating the properties of the "MyApp" so that it can be independent of the created "instance" (i.e. i no longer have to use the "instance.")?
First, if you create an setter function, it's a good idea to use it !! :D
$("#button").on("click", function(evt) {
// Update the name in MyApp to something else...
//instance.name = "john"
instance.updateName("john");
});
And then, it does not make sense to do put an event handler inside of a method of your object MyApp, since it will never bind the onclick event until you fire events()
Then... my way to organize this, is to use the jQuery document onload to bind all the DOM objects with the function of your applications. Usually something like this:
MYAPP = {};
MYAPP.say_something = function () {
alert('lol, you clicked me!');
};
...
$(function () {
$('#my_button').click(MYAPP.say_something);
$('#other_element').mouseenter(MYAPP.another_method);
});
And for big applications, where you have to work with a lot of elements, you can organize your code much better if you have a namespace for your DOM elements, something like this:
MYAPP.fetch_dom = function () {
return {
my_button: $('#my_button'),
other_element: $('#other_element')
};
};
And you can bind the events in a very neat way
$(function () {
// first initiate DOM
my_dom = MYAPP.fetch_dom();
// Then bind events
my_dom.my_button.click(MYAPP.say_something);
my_dom.other_element.mouseenter(MYAPP.another_method);
});
This way you don't have to look for the specific elements in the DOM from a thousand points of your programme, spreading hardcoded id's everywhere and performing noneffective searches against the DOM structure.
Finally, it is much better to use literals in JS rather than using the word new. JS is a prototypical OOP language and new is a little bit "against nature" that can be a pain in the ass.
I keep track of deleted objects in an observableArray called 'Deletions'. I parse that array in the UI to create 'undo deletion' links, but I can't get this to work. The code is very straight-forward and looks like this:
this.removePage = function(page){
self.formBuilder.pages.destroy(page);
var newDeletion = new Deletion();
newDeletion.element(page);
self.deletions.push(newDeletion);
}
this.removeFormElement = function(element){
self.formElements.destroy(element);
var newDeletion = new Deletion();
newDeletion.element(element);
builder.deletions.push(newDeletion);
}
var Deletion = function(){
var self = this;
this.element = ko.observable();
};
Note that different types of elements can be added to the Deletions observableArray. The only thing I need to do in the 'unremove' function, is setting the 'destroy' flag to false. But, I can't get that to work:
this.unremovePage = function(deletion){
deletion.element()._destroy(false);
}
What's the correct way of doing this?
EDIT
I can't get this working for the nested FormElements. The structure is: my main ViewModel is called 'FormBuilder'. The FormBuilder has multiple Pages (those are ViewModels themselves) and each Page has multiple FormElements (see code snippet above).
I can 'undelete' those FormElements, but I have no clue how to force a refresh on them.
this.unremove = function(deletion){
//console.log(deletion.element);
deletion.element()._destroy = false;
self.deletions.remove(deletion);
self.formBuilder.pages.valueHasMutated(); // works
deletion.element().valueHasMutated(); // this doesn't work
self.formBuilder.pages.indexOf(deletion.element()).valueHasMutated(); // neither does this
self.deletions.valueHasMutated(); // works
};
FormBuilder is the main ViewModel;
FormBuilder has an observableArray called Pages, each Page is a ViewModel;
Each Page has an observableArray called FormElements, each FormElement is a ViewModel;
FormBuilder has an observableArray called Deletions, each Deletion is a ViewModel and each Deletion contains an element, either a Page or a FormElement.
The problem:
I use the function 'unremove' to set the 'destroy' property of the element (either Page or FormElement) to false. As you can see, I then call 'valueHasUpdated' on pages. But how do I call that on the observableArray formElements as contained by an individual Page?
_destroy is not an observable. So, what you can do it set _destroy to false and then call valueHasMutated on the observableArray, so that any subscribers (the UI) knows that it may need to make updates.
So, you would want to deletion.element()._destroy = false; and then call self.deletions.valueHasMutated().
Is it inadvisable to add methods to a JQuery element?
eg:
var b = $("#uniqueID");
b.someMethod = function(){};
Update
Just to clarify, I am working on a JS-driven app that is binding JSON data to local JS objects that encapsulate the business logic for manipulating the actual underlying DOM elements. The objects currently store a reference to their associated HTML element/s. I was thinking that I could, in effect, merge a specific instance of a jquery element with it's logic by taking that reference add adding the methods required.
Well, there's nothing inherently wrong with it. It is, however, pretty pointless. For example:
$('body').someMethod = function(){};
console.log($('body').someMethod); // undefined
You are attaching the new function only to that selection, not to all selections of that element.
What you should do instead is to add a new function to jQuery.fn, which is a shortcut for jQuery.prototype:
jQuery.fn.someMethod = function() {
if (this[0].nodeName == 'body') {
// do your function
}
return this; // preserve chaining
};
The problem is that your function would be quite transient. A further requery and it will be gone. You can extend the jQuery object itself by $.fn.someMethod = function() {} and this method will be available for all queries.
$.fn.someMethod = function() {}
var b = $("body");
b.someMethod();
Or you can create a jQuery plugin. You can define a plugin this way:
$.fn.someMethod = function(options) {
# ...
});
Call it using $('body').someMethod();
Trying to make a make generic select "control" that I can dynamically add elements to, but I am having trouble getting functions to work right.
This is what I started with.
$select = $("<select></select>");
$select.addOption = function(value,text){
$(this).append($("<option></option>").val(value).text(text));
};
This worked fine alone but anytime $select is .clone(true)'ed the addOption() function is lost.
This is my object approach but still the function does not work.
function $selectX() {
return $("<select></select>");
}
$selectX.prototype.addOption() = function(value,text){
$(this).append($("<option></option>").val(value).text(text));
};
Hack solution is to add the function manually after creation:
$nameSelect= new $selectX;
$nameSelect.addOption = function(value,text){
$(this).append($("<option></option>").val(value).text(text));
};
Am I barking up the wrong tree?
To add new method to jQuery You need to use jQuery.fn.methodName attribute, so in this case it will be:
jQuery.fn.addOption = function (value, text) {
jQuery(this).append(jQuery('<option></option>').val(value).text(text));
};
But keep in mind that this addOption will be accessible from result of any $() call.