add object to observable array using knockout - javascript

For some reason i'm having trouble passing an object to observable array.
function CalendarViewModel() {
var self = this;
self.event = {
name : ko.observable(""),
adress : ko.observable(""),
startTime : ko.observable(""),
endTime : ko.observable("")
}
self.events = ko.observableArray([
])
self.addEvent = function (event) {
self.events.push(event);
alert(self.events.length)
alert(self.events[0].name());
}
my view:
<fieldset class="add-event-fieldset">
<div data-bind="with: event">
<legend>Add Event</legend>
<div style="text-align: center;">
<label class="title-label">What </label>
</div>
<div>
<label>Name: </label>
<input type="text" name="whatTxtBox" data-bind="value: name" />
</div>
<div>
<label>Where: </label>
<input type="text" name="whereTxtBox" data-bind="value: adress" />
</div>
<div style="text-align: center;">
<label class="title-label">When </label>
</div>
<div>
<label>start: </label>
<input type="text" id="startHourTxtBox" data-bind="value: startTime" />
</div>
<div>
<label>end: </label>
<input type="text" id="endHourTxtBox" data-bind="value: endTime" />
</div>
</div>
<input type="hidden" name="" id="hiddenDay" />
<button id="btnAddNewEvent" data-bind="click: $root.addEvent">+</button>
</fieldset>
The alerts show that the array is always empty, please explain what i'm doing wrong thanks.

Your observable array usage e.g self.events.push(event); is correct (because observable array implements push), only your alerts are wrong.
The correct calls would be
alert(self.events().length)
alert(self.events()[0].name());
Because you need to call the observable array as a function like the regular ko.observable to get its underlying value the array itself.
However you are currently adding to whole CalendarViewModel to the array because the btnAddNewEvent is outside of yourwith binding so the current context will be your main view model.
One way to solve it: just add the self.event to the array, so:
self.addEvent = function()
{
self.events.push(self.event);
alert(self.events().length)
alert(self.events()[0].name());
}
But this can cause problems later when you want to add another element because you will end up referencing the same element, so the right solution is copy the properties somewhere:
So I would create a constructor function for your event class:
var Event = function(data) {
var self = this;
self.name = ko.observable(data.name()),
self.adress = ko.observable(data.adress()),
self.startTime = ko.observable(data.startTime()),
self.endTime = ko.observable(data.endTime())
}
And push a new event in the addEvent
self.events.push(new Event(self.event));

Try
self.events().length
and
self.events()[0].name()
instead.

Just updating a little on nemesev answer. You really don't have to pass data as an argument
var Event = function() {
var self = this;
self.name = ko.observable();
self.adress = ko.observable();
self.startTime = ko.observable();
self.endTime = ko.observable();
};
And call it like
self.events.push(new Event());

Related

SOLVED (sloved by creating a new firebase project with a new database )- javascript wont write data in firebase realtime database

i have been trying to store data on firebase using javascript through html input form
this is my html form
i have tried every possible solution but it simply wont write even though the same code worked before on the same database
so my question is what is the wrong with my code and why it suddenly stopped working
<form action="" method="post" id="myform"><br>
<input id="tournumber" type="text" placeholder="tour number" /><br> <br>
<input id="tname" type="text" placeholder="name" /> <br><br>
<input id="arname" type="text" placeholder="email" /> <br><br>
<input id="enname" type="text" placeholder="date" /> <br><br>
<input id="duration" type="text" placeholder="national Id" /><br><br>
<input id="guide" type="text" placeholder="national Id" /> <br><br>
<input id="date" type="text" placeholder="national Id" /> <br><br>
<br><br>
<button id = 'submit' type="submit">Submit</button>
</form>
and this is my js script
function sub()
{
var tournumber= document.getElementById('tournumber').value;
var tname= document.getElementById('tname').value;
var arname= document.getElementById('arname').value;
var ename= document.getElementById('enname').value;
var duration= document.getElementById('duration').value;
var guide= document.getElementById('guide').value;
var tdate= document.getElementById('date').value;
var artype = "test";
var entype = "Guided by a guide";
savedata(tournumber, tname, arname, ename, duration, guide, tdate, artype, entype);
}
function savedata(tournumber, tname, arname, ename, duration, guide, tdate, artype, entype)
{
var tourRef = firebase.database().ref('tours/Available_tours')
.child(parseInt(tournumber))
.set({dayAR:arname, dayEN:ename, duration:duration,
guide:guide, tourDate:tdate, tourName:tname, typeAR:artype, typeEN:entype});
}
I believe that your problem is caused by method="post" in that when you click the Submit button, the browser submit's the form to the new path specified by action="" (which resolves to the same page).
Removing the method="post" and changing the onclick from the <a> to the form's onsubmit handler yields the following code:
<form action="#" onsubmit="return sub(this)" id="myform"><br>
<input id="tournumber" type="text" placeholder="tour number" /> <br><br>
<input id="tname" type="text" placeholder="name" /> <br><br>
<input id="arname" type="text" placeholder="email" /> <br><br>
<input id="enname" type="text" placeholder="date" /> <br><br>
<input id="duration" type="text" placeholder="national Id" /> <br><br>
<input id="guide" type="text" placeholder="national Id" /> <br><br>
<input id="date" type="text" placeholder="national Id" /> <br><br>
<br><br>
<button id="submit" type="submit">Submit</button>
</form>
By passing in the form using return sub(this), you can eliminate many document.getElementById(id) calls and replace them with form[id].
function sub(form) {
var tournumber = form['tournumber'].value;
var tname = form['tname'].value;
var arname = form['arname'].value;
var ename = form['enname'].value;
var duration = form['duration'].value;
var guide = form['guide'].value;
var tdate = form['date'].value;
var artype = "test";
var entype = "Guided by a guide";
savedata(tournumber, tname, arname, ename, duration, guide, tdate, artype, entype);
return false; // tell form to stay on this page
}
Other suggested changes
In your savedata() function, you should return the Promise returned by set() so that you can handle any errors.
function savedata(tournumber, tname, arname, ename, duration, guide, tdate, artype, entype)
{
return firebase.database().ref('tours/Available_tours')
.child(parseInt(tournumber))
.set({dayAR:arname, dayEN:ename, duration:duration,
guide:guide, tourDate:tdate, tourName:tname, typeAR:artype, typeEN:entype});
}
// e.g. savedata(...).then(() => console.log('saved successfully'), (err) => console.error('save failed: ', err))
I'd avoid calling a function with so many arguments as adding or removing data entries could be a pain in the future, and instead opt to pass an object instead.
function sub(form) {
var tournumber = form['tournumber'].value;
var newData = {
dayAR: form['arname'].value,
dayEN: form['enname'].value,
duration: form['duration'].value,
guide: form['guide'].value,
tourDate: form['date'].value,
tourName: form['tname'].value,
typeAR: "test",
typeEN: "Guided by a guide"
};
savedata(tournumber, newData);
return false; // tell form to stay on this page
}
function savedata(tournumber, data)
{
return firebase.database().ref('tours/Available_tours')
.child(parseInt(tournumber))
.set(data);
}
You have to write this information to a specific document with and ID.
For example:
function savedata(SOME_ID,tournumber, tname, arname, ename, duration, guide, tdate, artype, entype)
{
var tourRef = firebase.database().ref('tours/Available_tours/'+SOME_ID)
.child(parseInt(tournumber))
.set({dayAR:arname, dayEN:ename, duration:duration,
guide:guide, tourDate:tdate, tourName:tname, typeAR:artype, typeEN:entype});
}
You can find more about this here: https://firebase.google.com/docs/database/web/read-and-write

Nested foreach binding not displaying correctly

I'm trying to display an observableArray within an observableArray.
It's a simple ToDo list where the tasks are assigned to certain people and I want to display each persons tasks in there own div.
I'm using knockoutjs 3.3.0
Why aren't the Tasks showing up under the person?
Here's my HTML:
<div>
<form data-bind="submit: addPerson">
<p>New Person: <input data-bind='value: personToAdd, valueUpdate: "afterkeydown"' />
<button type="submit" data-bind="enable: personToAdd().length > 0">Add</button>
</p>
</form>
<form data-bind="submit: addTask">
<p>New Task: <input data-bind='value: taskToAdd, valueUpdate: "afterkeydown"' />
<select data-bind="options: people, optionsText: 'name', value:selectedPerson"></select>
<button type="submit" data-bind="enable: taskToAdd().length > 0">Add</button>
</p>
<fieldset>
<legend>Tasks</legend>
<div data-bind="foreach: people">
<div style="float: left; padding-right: 20px;">
<label data-bind="text: name" />
<div data-bind="foreach: tasks">
<input type="checkbox" data-bind="checked: done" />
<label data-bind="text: description, style: { textDecoration: done() ? 'line-through' : 'none' }" />
</div>
</div>
</div>
</fieldset>
</form>
</div>
Here's my javascript:
var ToDoList = function (people) {
var self = this;
self.taskToAdd = ko.observable("");
self.personToAdd = ko.observable("");
self.selectedPerson = ko.observable("");
self.people = ko.observableArray(people);
self.addTask = function () {
if (self.taskToAdd() != "") {
var person = ko.utils.arrayFirst(self.people(), function (item) {
return item.name() === self.selectedPerson().name();
});
person.addTask(new Task(self.taskToAdd(), person.name()));
self.taskToAdd("");
}
};
self.addPerson = function () {
if (self.personToAdd() != "") {
self.people.push(new Person(self.personToAdd()));
self.personToAdd("");
}
}.bind(self);
};
var Task = function (task, assignee) {
var self = this;
self.task = ko.observable(task);
self.assignee = ko.observable(assignee)
self.done = ko.observable(false);
self.description = ko.pureComputed(function () {
return self.task() + " (Assigned to: " + self.assignee() + ")";
}, self);
};
var Person = function (name, tasks) {
var self = this;
self.name = ko.observable(name);
self.tasks = ko.observableArray(tasks);
self.addTask = function (task) {
self.tasks.push(task);
}.bind(self);
};
ko.applyBindings(new ToDoList());
The reason the tasks are not appearing is because your <label> tags are not closed correctly. Instead of <label data-bind="blah"/>, use <label data-bind="blah"></label>.
The tasks container is not currently rendering at all, and therefore not parsed by knockout.
To be more clear, the label with data-bind="text: name" is not closed properly AND has a text binding. The text binding is replacing the entire tasks container with the name of the person. There are two instances of this error in your sample.

Highlight when add with KnockoutJS

The goal
Highlight item when I add it to another list using KnockoutJS.
The problem
I do not how to do, and yes — I have already searched on Google and Stack, but no success; no with "add".
My HTML markup:
<div class="tooltip-quantity">
<p class="float-left">Quantity:</p>
<form data-bind="submit: Summary.addToSummary">
<input class="quantity float-left" name="productQuantity"
maxlength="2"
type="text"
data-bind="value: ProductLayout.itemQuantity,
valueUpdate: 'afterkeydown'" />
<span class="float-left">/#(Model["MeasureName"])(s)</span>
<button class="btn btn-add btn-mini float-right"
data-bind="enable: ProductLayout.itemQuantityValid">
Add
</button>
<input type="hidden" name="productId" value="#Model["ProductId"]" />
<input type="hidden" name="productName" value="#Model["ProductName"]" />
<input type="hidden" name="productMeasure" value="#Model["MeasureName"]" />
</form>
</div>
My SummaryViewModel, on JS:
function SummaryViewModel() {
var self = this;
self.products = ko.observableArray();
self.addToSummary = function (formElement) {
var $productId = $(formElement).children("[name=productId]").val();
var match = $(".summary")
.find("li[data-product-id=" + $productId + "]").length;
if (!match) {
var $productName = $(formElement)
.children("[name=productName]").val(),
$productMeasure = $(formElement)
.children("[name=productMeasure]").val(),
$productQuantity = $(formElement)
.children("[name=productQuantity]").val();
self.products.push
(new Product
($productId, $productName, $productMeasure, $productQuantity));
$.ajax({
type: "POST",
url: "/ProductsSummary/Add",
data: { productId: $productId, productQuantity: $productQuantity }
});
}
}
};
Observation: addToSummary function performs what happens when I add something to a list.
Here is a working example, for which every time your add an item to a list, it is animated : here is a jsfiddle
html:
<script type="text/html" id="tmpl">
<div>
<span data-bind="text: $data"> </span>
<span> other stuff not bound </span>
</div>
</script>
<div data-bind="template: {name: 'tmpl',foreach: Data, afterRender: $root.Animate}">
</div>
<span data-bind="text: Data().length"></span>
<button data-bind="click: AddAnItemAndAnimate">AddAnItemAndAnimate</button>
Javascript :
function ViewModel() {
var self= this;
self.Data = ko.observableArray([]);
self.AddAnItemAndAnimate = function () {
//just after the push, when the elements will be visible, the function
//Animate will be call (it is linked to the afterRender of the tempalte)
self.Data.push('added');
};
self.Animate = function(elements, index, data){
// elements contains the html representing the new item
$(elements).filter('div').fadeOut().fadeIn();
};
}
var vm = new ViewModel();
ko.applyBindings(vm);

display ko.observable with html binding

Check out this fiddle: http://jsfiddle.net/XuMzS/4/
html:
<input data-bind="value: Total" type="text" />
<textarea cols="50" rows="10" data-bind="value: testHtml, valueUpdate: 'afterkeydown'">
</textarea>
<p>Html:</p>
<div class="wrapper">
<div data-bind="html: testHtml"></div>
<br />
</div>
javascript:
function viewModel() {
var self = this;
self.Total = ko.observable("1337");
self.testHtml = ko.observable();
}
ko.applyBindings(new viewModel());
What I am trying to do is to display the observable Total by writing the code that is needed inside the textarea (which displays html in the div below it).
Like if I wrote:
<span data-bind="text: Total"></span>
But nothing is displayed if I write that code in. Otherwise normal html is working.
Is there some way you can do this?
I made a sample, I think this is what you are looking for.
function viewModel() {
var self = this;
self.Total = ko.observable("1337");
self.testHtml = ko.observable("<b>test</b><span data-bind=\"text: Total\"></span>");
self.testHtmlWrapper = ko.computed(function () {
return '<div id="dynamicContent">' + self.testHtml() + '</div>';
});
self.rebind = function () {
try {
ko.applyBindings(self, document.getElementById("dynamicContent"));
} catch (e) {
}
};
self.testHtml.subscribe(self.rebind);
}
var vm = new viewModel();
ko.applyBindings(vm);
vm.rebind();
See Fiddle
I hope it helps.
Why do you need the testHtml observable?
This can easily be accomplished using the code below.
Viewmodel:
function viewModel() {
var self = this;
self.Total = ko.observable("1337");
}
ko.applyBindings(new viewModel());
Html:
<input data-bind="value: Total" type="text" />
<textarea cols="50" rows="10" data-bind="valueUpdate: 'afterkeydown'">
<b>test</b><span data-bind="text: Total"></span>
</textarea>
<p>Html:</p>
<div class="wrapper">
<div><b>test</b><span data-bind="text: Total"></span></div>
<br />
</div>
See this fiddle

Executing callback defined in element atrribute

Am trying to put together a form validation script, and i hit a snag when i wanted to have callback functions in my validation rules. The validation rules are defined in the html using data attributes
here the html
<div class="clearfix">
<input type="text" name="first_name" placeholder="First Name" id="_fname" data-required='yes' />
<span class="form-validation-status"></span>
<div class="form-error-msg"></div>
</div>
<div class="clearfix">
<input type="text" name="last_name" placeholder="Last Name" id="_lname" data-required='yes' />
<span class="form-validation-status"></span>
<div class="form-error-msg"></div>
</div>
<div class="clearfix">
<input type="text" name="email"
placeholder="Email Address" id="_email"
data-required='yes' data-has-callback="yes"
data-callback="email_check"
/>
<span class="form-validation-status"></span>
<div class="form-error-msg"></div>
</div>
<div class="clearfix">
<input type="text" name="username"
placeholder="Username" id="_username"
data-required='yes' data-has-callback="yes"
data-callback="username_check"
/>
<span class="form-validation-status"></span>
<div class="form-error-msg"></div>
</div>
<div class="clearfix">
<input type="password" name="password" placeholder="Password" id="_password" data-required='yes'/>
<span class="form-validation-status"></span>
<div class="form-error-msg"></div>
</div>
<div class="clearfix">
<input type="password" name="confpass" placeholder="Confirm Password" id="_confpass" data-required='yes' />
<span class="form-validation-status"></span>
<div class="form-error-msg"></div>
</div>
<div class="clearfix">
<input type="submit" value="Sign Up" class="btn btn-large btn-primary" id="signup-btn" />
</div>
I've been able to deal with the required rule, but i cant seem to figure what to do for the callbacks, this the javascript
var required_fields = $("input[data-required='yes']");
var has_callbac =$("input[data-has-callback='yes']");
/* VALIDATE REQUIRED FIELDS */
$.each(required_fields,function(index,item){
var field = $(item);
var status = field.next();
var field_name = field.attr("placeholder");
var error_msg = "The "+field_name+" cannot be empty";
var form_error = field.siblings('span.form-validation-status').next();
field.blur(function() {
var _field_val = ($(this).val());
form_error.html('');
status.removeClass('error-status');
if(_field_val == ""){
validation_errors++;
status.addClass('error-status');
form_error.html(error_msg);
}else {
status.addClass('ok-status');
validation_errors= validation_errors + 1 - 2;
}
});
});
/* VALIDATE FIELDS WITH CALLBACKS */
$.each(has_callbac,function(index,item) {
var field = $(item);
var status = field.next();
var form_error = field.siblings('span.form-validation-status').next();
var callback = field.attr("data-callback");
var callback_func = callback+"()";
var test = typeof callback_func;
//i got confused at this point
console.log(test);
});
Please help me out.
If these callback functions are global, you can simply do:
var callback = field.attr("data-callback");
window[callback]();
Or, if your callback would like to have the field in question set as the this value, then you'd do:
var callback = field.attr("data-callback");
window[callback].call(field); //and of course tack on any other parameters you have
If you've defined these callbacks as properties of some other object, then you'd do the same thing:
var callbackHolder = {
callback1: function() { },
callback2: function() { }
};
var callback = field.attr("data-callback");
callbackHolder[callback]();
you have this:
var callback = field.attr('data-callback');
Extend it to this:
// check if the value is a function in the global scope
if('function' == typeof(window[callback])) {
callback();
}
If the functions are not in the global scope, you may need to modify that logic.
EDIT:
If you are working in the local scope, you may benefit from adding a generic validation function such as:
(function($){
$.fn.form_validation=function(){
var rules = {
callback_one: function(fieldvalue) {
var response = {
'code':'failed',
'reason':'Missing or incomplete'
};
//some validation here
return response;
}, ...
}
var rule = '', args = [];
if(arguments.length > 0) {
if('object' === typeof(arguments[0])) {
args = arguments;
} else if('undefined' !== typeof(rules[arguments[0]])) {
args = Array.prototype.slice.call(arguments);
args.shift();
rule = arguments[0];
}
}
rules[ rule ].apply( this, args );
}
}($));
Then you could call something like:
var result = $.form_validation(callback,data);
within your $.each()
NOTE: untested - just a starting point to separate our your code into controllable modules rather than bring it all inline in case you need to reuse your validation or edit your code inline.

Categories

Resources