I'm attempting to alter a JSON in memory by using the knockout.js UI. The problem I'm running into is that when I alter a value in the UI, the JSON data doesn't seem to be changed whatsoever. I've implemented buttons with console.log(config) to test this. Any suggestions would be great, thanks!
http://jsfiddle.net/Hfwfs/1/
edit.js
var config = {
"departments": [
{
"name": "Step Down"
}, {
"name": "ER"
}]
};
var DepartmentViewModel = function (dep) {
var self = this;
self.name = ko.observable(dep.name);
}
function ConfigViewModel() {
var self = this;
self.departments = ko.observableArray([]);
ko.utils.arrayForEach(config.departments, function (dep) {
self.departments.push(new DepartmentViewModel(dep));
});
}
ko.applyBindings(new ConfigViewModel());
It is getting updated, you've to look in the model
Related
I will start my question by describing desired outcome:
I want to build an input form to post to my API using knockout JS, however the Entity object I want to input has foreign keys so I need to give a select option for all of the options in the foreign table.
Lessons View Model
var lessonRegisterViewModel;
function Lesson(id, name, teacher, room, subject, startTime, endTime) {
var self = this;
self.Id = ko.observable(id);
self.Name = ko.observable(name);
self.Teacher = ko.observable(teacher);
self.Room = ko.observable(room);
self.Subject = ko.observable(subject);
self.StartTime = ko.observable(startTime);
self.EndTime = ko.observable(endTime);
self.addLesson = function() {
var dataObject = ko.toJSON(this);
$.ajax({
url: '/api/Lessons',
type: 'post',
data: dataObject,
contentType: 'application/json',
success: function(data) {
lessonRegisterViewModel.lessonListViewModel.lessons.push(new Lesson(data.Id, data.Name, data.Teacher, data.Room, data.Subject, data.StartTime, data.EndTime));
self.Id(null);
self.Name('');
self.Teacher('');
self.Room('');
self.Subject('');
self.StartTime('');
self.EndTime('');
}
});
}
}
function LessonList() {
var self = this;
self.lessons = ko.observableArray([]);
self.getLessons = function() {
self.lessons.removeAll();
$.getJSON('/api/Lessons', function(data) {
$.each(data, function(key, value) {
self.lessons.push(new Lesson(value.id, value.name, value.teacher, value.room, value.subject, value.startTime, value.endTime));
console.log(self);
});
});
};
self.removeLesson = function(lesson) {
$.ajax({
url: '/api/Lessons/' + lesson.Id(),
type: 'delete',
contentType: 'application/json',
success: function() {
self.lessons.remove(lesson);
}
});
}
}
lessonRegisterViewModel = {
addLessonViewModel: new Lesson(),
lessonListViewModel: new LessonList()
};
$(document).ready(function() {
// bind view model to referring view
ko.applyBindings(lessonRegisterViewModel);
// load lesson data
lessonRegisterViewModel.lessonListViewModel.getLessons();
});
Example JSON we get out:
[
{
"id":1,
"name":"Lesson 1",
"teacher":{
"id":3,
"firstName":"Sophie",
"lastName":"Adams",
"emailAddress":"teacher3#foo.com"
},
"classroom":{
"id":1,
"name":"Great Hall"
},
"subject":{
"id":4,
"name":"jQuery"
},
"startTime":"2016-02-10T09:30:00",
"endTime":"2016-02-10T10:30:00"
},
{
"id":2,
"name":"Lesson 2",
"teacher":{
"id":4,
"firstName":"Tristan",
"lastName":"Sanchez",
"emailAddress":"teacher4#foo.com"
},
"classroom":{
"id":2,
"name":"Room 1A"
},
"subject":{
"id":3,
"name":"SQL"
},
"startTime":"2016-02-10T09:00:00",
"endTime":"2016-02-10T10:30:00"
}
]
So essentially I am inserting a lesson, which comprises of
Name
Teacher
Room
Subject
StartTime
EndTime
I need to go off, and offer dropdown lists for all the teachers in the database, I also need to do this for rooms and subjects. I have working View Models for each of the individual entities with no dependencies, so I can physically complete all CRUD on Teachers, Rooms and Subjects.
Any suggestions on how to implement this would be welcomed.
It looks like you don't have a "I can't make it do what I want" kind of issue, but more of a mental block about where things fit into your model. So let me suggest a different mental approach.
What you have built is a thorough data model of your primary data object. But your goal is to build is an application, which will need that data model, but also other things, all in its viewmodel.
A viewmodel is specified entirely by a view: you need to implement exactly the set of things that your view will use. So start by writing the HTML with its data bindings. If your application is going to have a <select> item that needs a list of rooms, just go ahead and assume such a member exists in your view model and make the binding.
Once you have laid out the application this way, you have fully specified your viewmodel. You've created an interface (all the viewmodel members) that you need to implement. The application — not the data — defines the viewmodel. So start with the application and leave the data modeling to the implementation phase.
Below is my Modal controller.
Se_chnl and Se_segn_rqst are the Loopback models.
I'm initializing the modal form in the first step.
The $scope.Se_chnl_find() is getting me a list from the backend which I load as a dropdown menu in the modal.
This call to loopback works fine.
Then later on once the form is filled, I call the submit function and in that I call the create function of loopback Se_segn_rqst.create($scope.rqst)
$scope.rqst contains the parameters for creating that rqst.
Now, once I have created this "rqst", I want to retrieve ID of the latest created request by that user and store it in the global variable.
But the loopback api/MySQL doesn't return anything.
Record is created in the backend when create is used.
But the find function doesn't work.
I tried the find filter in Strongloop/Loopback explorer and it works there.
Not sure why it is not returning anything when I tried it from the controller.
codeApp.controller('ModalInstanceCtrl', function($scope, $modalInstance, $state, Se_chnl, Se_segn_rqst) {
var defaultForm = {
cmpgn_nm: "",
cmpgn_id: "",
strgy_id: "",
rqst_typ_cd: "",
chnl_id: ""
}
$scope.channels = Se_chnl.find({
filter: {
"fields": {
"chnl_nm": true,
"chnl_id": true
}
}
});
$scope.rqst = angular.copy(defaultForm);
$scope.rqst.rqst_id = 0;
$scope.submit = function(reqForm) {
$scope.rqst.rqst_nm = $scope.rqst.cmpgn_nm;
$scope.rqst.rqst_stat_cd = 'DRAFT';
$scope.rqst.insrt_user_id = $scope.$parent.user_id;
$scope.rqst.insrt_dt = new Date();
Se_segn_rqst.create($scope.rqst);
$scope.$parent.requested_id = Se_segn_rqst.find({
filter: {
"fields": {
"rqst_id": true
},
"order": "insrt_dt DESC",
"limit": 1,
"where": {
"rqst_stat_cd": "DRAFT",
"insrt_user_id": "xyz123"
}
}
});
$modalInstance.dismiss('cancel');
};
$scope.resetForm = function(reqForm) {
$scope.rqst = angular.copy(defaultForm);
reqForm.$setPristine();
reqForm.$setUntouched();
};
});
This is the piece returning no value. I want an id in the requested_id global variable. The filter is performing correctly in the Strongloop explorer, so there is no syntax error.
$scope.$parent.requested_id = Se_segn_rqst.find({
filter: {
"fields": {
"rqst_id": true
},
"order": "insrt_dt DESC",
"limit": 1,
"where": {
"rqst_stat_cd": "DRAFT",
"insrt_user_id": "xyz123"
}
}
});
There is a bit more information needed, but we can discuss the rest here: https://groups.google.com/forum/#!topic/loopbackjs/qdPaorTpOAA
Below is the structure of JSON which I use to query an API
"order_items": [
{
"menu_item_id": "VD1PIEBIIG",
"menu_item_name": "Create Your Own",
"modifiers": [
{
"modifier_id": "6HEK9TXSBQ",
"modifier_name": "Shrimp"
}
],
"quantity": "1",
"total": 15.99,
"variant_id": "TXDOR7S83E",
"variant_name": "X-Lg 18\""
}
]
Now I want to call this API from an HTML page using Javascript(Using HTML elements like forms and drop down menus etc). I want to create a Javascript object with proper structure and then convert it to JSON using "stringify" function. But I am not able to create the Javascript object. Can anyone help with this?
Like i want to have the following structure
obj.order_items[0].menu_item_id="VD1PIEBIIG";
obj.order_items[0].menu_item_name="Create Your Own";
obj.order_items[0].modifiers[0].modifier_id="6HEK9TXSBQ";
and so on.
var jsonToSend = { "order_items": [ ] };
// then for each order item
var orderItem = { "menu_item_id": <whatever>,
"menu_item_name": <whatever>,
"quantity": <whatever>,
"total": <whatever>,
"variant_id": <whatever>,
"variant_name": <whatever>,
"modifiers": []
};
// then for each modifier
var modifier = { "modifier_id": <whatever>, "modifier_name": <whatever> };
orderItem.modifiers.push(modifier);
jsonToSend.order_items.push(orderItem);
JSON.stringify(jsonToSend);
Well there are a couple of ways to do this.
Manually create the Json object to send from the HTML elements:
$.ajax({
type: "POST",
url: "some.php",
data: new {"order_items": [
{
"total": $('total').Val(),
"variant_id": $('variant_id').Val(),
"variant_name": $('variant_name').Val()
}
]})
.done(function( msg ) {
alert( "Data Saved: " + msg );
});
You could use a great framework like KnockoutJs, this will keep your JSON object up to date with your form, so that you don't have to do it manually. When you are ready you just submit your original json back to the server.
See this basic example on JsFiddle
var ClickCounterViewModel = function() {
this.numberOfClicks = ko.observable(0);
this.registerClick = function() {
this.numberOfClicks(this.numberOfClicks() + 1);
};
this.resetClicks = function() {
this.numberOfClicks(0);
};
this.hasClickedTooManyTimes = ko.computed(function() {
return this.numberOfClicks() >= 3;
}, this);
};
ko.applyBindings(new ClickCounterViewModel());
You can use any number of plugins to Serialize the form, but the problem is getting the JSON structure just right.
See SerializeArray
$( "form" ).submit(function( event ) {
console.log( $( this ).serializeArray() );
event.preventDefault();
});
I am attempting to present a JSON collection, which is retrieved asynchronously and using FireBug I can see this ultimately looks like:
[{"Id":"00000010"},{"Id":"00000002"},{"Id":"00000003"}]
This does not work, but if I declare a collection as:
[{ "Id": "00000004" }, { "Id": "00000005" }, { "Id": "00000006"}]
This works, and then using FireBug I can see this is slightly different:
[Object { Id="00000004"}, Object { Id="00000005"}, Object { Id="00000006"}]
Why does it make a difference when retrieving the data synchronously and declaring a collection? What are my options for getting this to work.
Thanks.
UPDATE
I am also using sammy.js, here is the JavaScript:
var app = $.sammy('div[role="main"]', function () {
this.use('Mustache', 'html');
this.get('#/', function (context) {
this.load('/data')
.then(function (response) {
context.blah = 'blah';
context.data = response;
var data2 = [{ "Id": "00000004" }, { "Id": "00000005" }, { "Id": "00000006"}];
context.data2 = data2;
var templateUrl = '#Url.Content("~/Templates/template.html")';
context.partial(templateUrl);
});
});
});
$(function () {
app.run('#/');
});
Here is the template:
<h1>{{blah}}</h1>
<ul>
{{#data}}
<li>{{Id}}</li>
{{/data}}
</ul>
<ul>
{{#data2}}
<li>{{Id}}</li>
{{/data2}}
</ul>
Okay, I figured this one out!
context.data = response;
becomes
context.data = JSON.parse(response);
I'm attempting to use Backbone.js to simplify data (JSON) management and interaction with DOM.
Firstly, I'm not sure if Backbone.js can indeed simplify and improve the current process, but I'd like to assume it can.
Previously I'm retrieving the data with jQuery AJAX function. Now, I'm retrieving the data(still with AJAX) Backbone style into the Backbone model.
For update, previously I was parsing through the JSON object itself to update data. I would then send back the updated json to the back-end (just as I've received it).
Now, is it possible to use the set function in Backbone to simplify something like the below and ideally where should the set attribute behaviour (and all other UI bindings like change events) be constructed? Would it be on the fetch() success handler, which is in the View initializer?
function setBucketOffer(bucketName, newId) {
var segments = json.segments;
for (var i = 0; i < segments.length; i++) {
if (segments[i].market.toLowerCase() === g_market) {
var genders = segments[i].gender;
for (var i = 0; i < genders.length; i++) {
if (genders[i].name.toLowerCase() === g_segment) {
var buckets = genders[i].buckets;
for (var i = 0; i < buckets.length; i++) {
if (buckets[i].name === bucketName) {
buckets[i].confirm = newId;
return;
}
}
}
}
}
}
}
Example JSON
{
"segments": [
{
"market": "Market1",
"gender": [
{
"name": "male",
"buckets": [
{
"name": "Market1_M_CBD",
"subscribers": "50,000",
"postcode": "20000-2010",
"lastsend": "13/03/12 4:30PM",
"suggest": "10054",
"confirm": ""
},
{
"name": "Market1_M_North",
"subscribers": "50,000",
"postcode": "20000-2010",
"lastsend": "13/03/12 4:30PM",
"suggest": "10054",
"confirm": ""
}
]
},
{
"name": "female",
"buckets": [
{
"name": "Market1_F_CBD",
"subscribers": "50,000",
"postcode": "20000-2010",
"lastsend": "13/03/12 4:30PM",
"suggest": "10054",
"confirm": "10054"
}
]
}
]
},
{
"market": "Market2",
"gender": [
{
"name": "male",
"buckets": [
{
"name": "Market2_M_CBD",
"subscribers": "50,000",
"postcode": "20000-2010",
"lastsend": "13/03/12 4:30PM",
"suggest": "10054",
"confirm": "10054"
},
{
"name": "Market2_M_North",
"subscribers": "50,000",
"postcode": "20000-2010",
"lastsend": "13/03/12 4:30PM",
"suggest": "10054",
"confirm": "10054"
},
{
"name": "Market2_M_South",
"subscribers": "50,000",
"postcode": "20000-2010",
"lastsend": "13/03/12 4:30PM",
"suggest": "10054",
"confirm": "10054"
}
]
}
]
}
]
}
Edit 1
From here, I'm trying to make good use of Parse and to get just segments from my JSON:
var Offers = Backbone.Collection.extend({
url: 'URL',
parse: function (response) {
return response.segments;
}
});
Here, I'm getting more than just response.segments. Also not sure if it's right for me to use the render function or fetch success function to populate the DOM. Suppose I have my html template in the DOM... I want to clone it using jQuery clone() and populate the clone using a forEach on segments, and push back all the clones into the html body. Is this workable in backbone, how would you do it? (I'm able to do this without backbone.js, but would like to see how I can improve with backbone.js, and bind all the data on the clones to model changes)
var OfferView = Backbone.View.extend({
initialize: function () {
this.model = new Offers();
this.model.fetch({
success: function (collection, response) {
console.log(response);
}
});
this.model.on('change', this.modelChange);
this.model.on('change', this.render);
this.modelChange = function () {
alert('model changed');
};
},
render: function () {
}
});
Edit 2
I'm up to creating individual views through a forEach but am having trouble inserting these back into the DOM. What am I doing wrong? (Not sure around the return this part)
// DEFINE VIEW
var OfferView = Backbone.View.extend({
initialize: function () {
this.model = new Offers();
this.model.fetch();
this.model.on('change', this.modelChange);
this.model.on('change', this.render);
this.modelChange = function () {
alert('model changed');
};
this.render();
},
render: function () {
var self = this;
this.model.forEach(function (s) {
var view = new OfferMarketView({
id: "container" + s.get('name').toLowerCase().replace(/\s*/g, '')
});
$('#leftCol').append(view.el);
});
return this;
}
});
var OfferMarketView = Backbone.View.extend({
tagName: "div",
className: "marketContainer",
events: {},
render: function() {
}
});
Whenever you call fetch on a model the response is passed through a parse method that can be defined in your model. parse takes one parameter, the ajax response:
parse: function(response) {
}
In that function you can do whatever you want with the data that comes back from your ajax request and eventually return that object. The object returned by the parse method will be set on your model.
For event binding, you'll want to do that in your view. In the initialize method of your view you can do something like:
this.collection.on("change", this.someFunction);
Now, any time something causes that model to trigger its change event someFunction ( also defined in your view ) will run.
EDIT
The sample json you added to the question looks to be pretty normalized. With that data, I'd be fetching it into a collection. If that's the structure you want your models to look like then you don't need to do much parsing.
in you collection file if you create a parse method that does the following:
parse: function(response) {
return response.segments;
}
When you call your fetch, this.collection.fetch() on a successful request, your collection will be filled with models that contain attributes in a structure that matches your response.
EDIT 2
Your binding looks ok.
in this section of code:
this.collection.fetch({
success: function (model, attributes) {
initAll(attributes);
// populate ui with attributes from model
}
})
The parameters that are passed back on a success in a collection fetch are (collection, response) collection is the result of collection call and what this.collection will end up being. response is the response of your ajax request.
I'm not sure what initAll(attributes) is supposed to be doing. If you add a parse method like I posted above, your collection will contain a set of models with the attributes of each segment.
Also, rather than calling this.render() at the end, you could do bind render to the change event:
this.collection.on('change', this.render);
That way any time your collection changes, that view will automatically render again so your changes will show up.