Knockout Subscribe Not Firing After X-Editable Change - javascript

Use Case
I am trying to set up the x-editable library around a knockout view model that can live inside of a js datatables instance. On load, it would pull down the tabular data. A user would be able to edit one field of one row and that would save automatically. I also adding some client side validation.
Problem
For my problem and corresponding simplified example, I took the dataTables portion out.
variable.subscribe inside the knockout viewmodel never seems to fire after x-editable changes it. I don't see any errors. I've check against multiple examples. Currently, I don't have any more clues to go off of.
Versions
knockout: 3.4.0
x-editable: 1.5.1
valib: 2.0.0
HTML
<div id="simple-view">
<span class="editable" data-bind="text: testA"></span>
</div>
JS - Knockout View Model Portion
(function(){
'use strict';
function SimpleViewModel(){
var self = this;
self.testA = ko.observable("Simple");
self.testA.subscribe(function(changes){
console.log("name changes: " + changes.length);
});
}
ko.applyBindings(new SimpleViewModel(), document.getElementById('simple-view'));
})();
JS - Call to X-Editable
document.addEventListener("DOMContentLoaded", function(event) {
$(".editable").editable({
type: 'text',
url: function(params){
console.log("url test: " + JSON.stringify(params));
},
mode: 'inline',
anim: false,
validate:function(value){
var result = valib.String.match(value, /^[a-zA-Z0-9-_]+$/);
console.log("inside validation: " + result);
if(!result){
return "This is a test validation response";
}
}
});
});
Problem Recap
I can see console logs for firing the validation message and for the url parameter (which here is practically an on submit for x-editable). However, I have never seen the subscribe function work. In my non canned example code, I was also working with observable arrays. Didn't work there. Does subscribe simply not pick up changes from the browser side but instead only when the view model changes (e.g. when some code pushes data onto an observable array?)
P.S.
I was originally trying to get knockout-x-editable to work with knockout-validation. However, k-x-e never worked after trying every example I could find and stepping through the plugin code. Something was expected from k-x-e for the ko-validation plugin to work but it wasn't firing an error message. As such, I eventually hit a brick wall I could get past and decided to try the above approach.

You need x-editable custom binding to enable x-editable play nice with knockoutjs.
X-editable custom binding for knockoutjs is available here. Include this binding after knockoutjs in your page.
So you will do
<div id="simple-view">
<span class="editable" data-bind="editable: testA, editableOptions:{/* your x-editable options here type, url, mode etc */}"> </span>
</div>
and your JS - Knockout View Model Portion
(function(){
'use strict';
function SimpleViewModel(){
var self = this;
self.testA = ko.observable("Simple");
self.testA.subscribe(function(changes){
console.log("name changes: " + changes.length);
});
}
ko.applyBindings(new SimpleViewModel(), document.getElementById('simple-view'));
})();
and then your xeditable controls will be in-sync with your observables.

Related

Attribute-based helper with lambda expressions for data-bind for integrating knockout.js with MVC4/5 + Razor

I just tried KnockoutMvc and while I admire the creator's effort, I find it is a little too heavy-handed to be useful in general. However I like to minimize the amount of JavaScript in my Razor views, especially boilerplate JavaScript (like binding my serverside view models to clientside view models).
Is there any library out there that allows you to use syntax like:
<span data-bind="#Html.DataBindFor(m => m.MyProperty)"></span>
Alternatively, if I were to go about trying to write my own, approximately what (very general) components would I need to make the library useful? I am assuming at a minimum I would need:
A custom set of attributes for defining how each model / property / method would be bound
An extension method for use in Razor views to use reflection to find out the attribute values
An extension method for use in Razor to generate the (minimal?) JavaScript to bind the client view model to the Razor Model and functions to call the server while necessary
Finally, assuming this library doesn't exist, is there a good reason it doesn't exist? IE is there no way to really work this problem in generality such that a helper method is, uh, helpful?
The reason I ask is because the library Lib.Web.Mvc.JqGrid has helped me create interactive tables really quickly with absolutely minimal amounts of JavaScript (just enough to format columns and such) and I wonder why the same does not exist for knockout.js.
The get rid of manually translating the server-side models to the client-side view models, you can employ the ko.mapping plugin which is then used like this:
#model ViewModel
<script src="#Url.Content("~/Scripts/knockout.mapping-latest.js")" type="text/javascript"></script>
...
<script type="text/javascript">
function ViewModel(initData) {
var self = this;
ko.mapping.fromJS(initData, {}, self);
// Add custom view model logic
...
// Logic for persisting the current state of the model back to the server
self.Save = function() {
$.ajax('/model-uri', {
type: 'POST',
contentType: "application/json",
data: ko.toJSON(self),
success: function (response) {
alert('Success!');
},
error: function(xhr, status, error) {
alert('Error: ' + xhr.responseText);
}
});
};
var initialData = #(Html.Raw(JsonConvert.SerializeObject(Model)));
var model = new ViewModel(initialData);
ko.applyBindings(model);
</script>
Note that we're serializing the server-provided view model to a JavaScript object and then making its properties observable using the ko.mapping plugin which saves us duplicating the view model definition on the client side. We also employ the ko.toJSON utility function when sending the updated model back to the server.
The mapping procedure can be customized to ignore certain properties:
var mapping = {
'ignore': ["propertyToIgnore", "alsoIgnoreThis"]
}
ko.mapping.fromJS(data, mapping, self);
There are more mapping customizations available, described on the plugin page.

Backbone/jQuery mobile duplicate view

I've got some problems with my Backbone, RequireJS & jQuery mobile application.
When I use a form view 2 times the form submit event is fired twice.
Each new view is added to the body and the previous view will be removed. For that I use this code in my app.js file:
$(document).on("mobileinit", function () {
$.mobile.linkBindingEnabled = false;
$.mobile.hashListeningEnabled = false;
$(document).on('pagehide', 'div[data-role="page"]', function (event, ui) {
$(event.currentTarget).remove();
});
})
Router.js
define([
'jquery',
'backbone',
'views/projects/ProjectsView',
'views/projects/AddProjectView'
], function($, Backbone, ProjectsView, AddProjectView) {
return Backbone.Router.extend({
routes: {
'addProject': 'addProject',
'editProject/:projectId': 'editProject',
'*actions': 'showProjects' // Default
},
addProject: function() {
new AddProjectView().render();
},
editProject: function(projectId) {
require([
"views/projects/EditProjectView",
"collections/ProjectsCollection",
"models/ProjectModel"
], function (EditProjectView, ProjectsCollection, ProjectModel) {
var projectsCollection = new ProjectsCollection();
projectsCollection.fetch();
var project = projectsCollection.get(projectId);
if (project) {
var view = new EditProjectView({model: project, projectsCollection: projectsCollection});
view.render();
}
});
},
showProjects: function() {
new ProjectsView().navigate();
}
});
});
I've uploaded my code to a directory on my website: http://rickdoorakkers.nl/np2
If you go through the following steps, you'll see the problem:
Add a project
Add a second project with a different name
Open a project by clicking on it and change the values and save it
As you can see the event of adding a project is launched and there's a project added instead of changed.
This same problem also occurs when you try to change 2 projects after each other. The first project is edited then.
Is there somebody who can tell me what I'm doing wrong?
Thanks!
Rido, your code is really hard to read for me because of the way it's mixing together a few things and not following any of the usual conventions for Backbone.
For your specific problem, I have a feeling the problem is that you binding both the Edit view and the New view to body (el: body), both respond to the event submit, and you are never clearly cleaning up the views, so I think that whenever you add a project and then edit it, the add view is still in memory, still bound to the submit event and still answering the call = new project with the new name, instead of editing.
It's 'easy' to fix in a dirty way, by adding a call to stopListening after adding, but the real problem is that you are binding to body, and mixing togethers the Backbone Router and manual hash control + other weird patterns, such as fetching the collection every 5 lines (you could just create one at the start of the App and always use it! here it's localstorage so it doesn't matter but if you ever move to a remote storage, you'll regret it... fetch() reset the collection and do a full reload!). May I suggest you maybe try to rewrite this without caring about jQuery mobile and just try to make it work with Backbone.Router + a single collection + not binding to body but instead, create the views on the fly and append them to body / remove when you are done? You'll see the bugs will be less weird and easier to track.

How to remove the "name" param in for fields in ExtJS 4

I am integrating a payment provider into a ExtJS websites.
Basically, a form needs to be created and the form fields is send to the payment provider using Ajax.
The problem is that the payment provider does not allow that the form fields has a "name" param assigned to the "" tag. They do a manual check of the implementation and makes sure it is not there.
I assume it is a counter-mesasure for when the visitor has Ajax dissabled and the form gets submitted to my server instead, revealing the credit card. I know it does not make any sense with ExtJS, as it would not work without Javascript turned on, but non-the-less, that is the rule from the payment provider.
So, how can I force ExtJS to not put a "name" param in the form field? I have tried putting "name: ''" into the fields, but that gets ignored.
Do I use the template-system in ExtJS to solve this?
So Eric is perfectly right that it can be done much easier then modifying the whole template but non the less I would use a plugin for such a special case. I made a quick one:
Ext.define('Ext.form.field.plugin.NoNameAttribute', {
extend: 'Ext.AbstractPlugin',
alias: 'plugin.nonameattribute',
init: function(cmp) {
Ext.Function.interceptAfterCust(cmp, "getSubTplData", function(data){
delete data['name'];
return data;
});
}
});
Note the used method interceptAfterCust is a custom one of mine that modify the existing one by handing the result of the original to the intercepting one as argument. It is also using the given original object (which can be threaten as a scope) as scope for the original method. The easiest would be to add these method to Ext.Function
Ext.Function.interceptAfterCust = function(object, methodName, fn, scope) {
var method = object[methodName] || Ext.emptyFn;
return (object[methodName] = function() {
return fn.call(scope || this, method.apply(object, arguments));
});
}
Here is a working JSFiddle where the first field will not have a name attribute on the dom even if it exist in the component.
There's a surprisingly simple solution to this. I tested it with Ext.form.field.Text and Ext.form.field.ComboBox and it works well, but I don't know if it works for all form fields or if there are any negative side-effects. Use with caution.
Ext.define('Override.form.field.Base', {
override: 'Ext.form.field.Base',
getSubTplData: function(){
var data = this.callParent(arguments);
delete data.name;
return data;
}
});
Basically, it removes the auto-generated name from the render data before passing it along. The best part is that no private methods are involved so this should be a stable solution.
I prefer this in the field config options:
submitValue: false
Available since ExtJS 3.4

knockout view model to represent a single object

Edit: This answer here seems to have provided the solution; because I am a lazy sod and was trying to avoid having to define my model in two places (once on server, once on client) I figured there had to be a way. By using the custom binding in the linked solution, I'm able to have the observables created from the various form element data-bind attributes, so basically it builds the model from the form. So it's effectively driving the model definition from the form. I haven't decided yet if this is a bad idea :)
I'm wondering what I'm doing wrong (or indeed, if I even am doing anything wrong). I need to create a form to edit a single record at a time, which has just got some simple text/number properties:
{ItemCode:ABCD,LotNumber:1234,ID:4885,MeasuredValue1:90}
I decided to use ko with the mapping plugin to do it. I'm fairly new to ko.
Anyway I ended up with a view model like this:
var LotModel = function() {
var self = this;
self.Update = function(itemcode,lotnumber) {
var data = { ItemCode: itemcode, LotNumber: lotnumber }
//DoAjax is just a utility function and is working fine.
DoAjax("POST", "LotHistory.aspx/GetLotHistory", data,
function(msg) {
ko.mapping.fromJS(msg.d, null, self);
ko.applyBindings(self);
},
function(xhr, ajaxOptions, thrownError) {
AjaxFailure(xhr, ajaxOptions, thrownError);
}
);
}
}
And later on my code,
var lm = new LotModel();
and finally in $(document).ready
ko.applyBindings(lm);
Now it works, except that if you see in the view model, every time I load data I have to re-call ko.applyBindings(self) in the vm's Update function.
If I comment out that line, it doens't bind. I think that this is because I'm only binding a single object (i.e the view model itself is the object after the ko mapping plugin does its work) but everywhere I read about ko it seems to say "you only need to call this once, ever."
So I can't help feeling I am missing something really obvious; commenting out ko.applyBindings(lm) in the document ready function doesn't make any difference because I automatically call lm.Update in document.ready but commenting it out in the viewmodel breaks it.
So my question is - am I doing this the wrong way? Is it overkill for just a single object at a time type binding? I mean it doesn't bother me too much, it works as I want it to but still, it's nagging at me...
It's indeed best not to reapply bindings many times if avoidable. The problem is that you don't have any observable properties in your viewmodel to begin with. An initial call to ko.mapping.fromJS can fix this (or you can manually add the observables) e.g.:
ko.mapping.fromJS({
ItemCode: '', LotNumber: 0, ID: 0, MeasuredValue1: 0
}, null, self);
See fiddle for a working example: http://jsfiddle.net/antishok/qpwqH/1/

Loading data with dependentObservable causing an infinite loop

I'm playing around with Knockout and now trying to use the Knockout address plugin (based on jQuery address).
This code below works, except that when I try entering the address the linkObservableToUrl provides the page is loaded without the right tags. I guess something is wrong in the way I'm loading the messages, but I'm not sure how this should be done using the Knockout framework.
I've got the following code, which is causing an infinite loop:
var viewModel = {
page: ko.observable(1),
//messages: ko.observableArray([]),
tags: ko.observable()
};
viewModel.filterTags = function (filterTags) {
viewModel.tags(filterTags);
};
viewModel.messages = ko.dependentObservable(function () {
$.ajax(
// abbreviated
data: ko.toJSON(viewModel),
// abbreviated
)}, viewModel);
ko.applyBindings(viewModel);
ko.linkObservableToUrl(viewModel.tags, "tags", null);
How can I solve this and still have the messages depend on page and tags?
Switch to AngularJS. Angular's databinding is much better than Knockout's. Much of the problems you are encountering here with infinite loops, etc. are due to Knockout's need for observable wrappers.
Angular does not require observable wrappers of your objects. Angular can observe any standard JSON/Javascript object, and databind directly to any HTML element via MVVM.
In Angular, you would simply make your AJAX call for ViewModel.messages, and the standard JSON would be applied to your ViewModel.messages property. No observable wrappers. This eliminates the need for ko.dependentObservable() and thus - removes your infinite loop.
http://www.angularjs.org
In the second example (which is quit long for a code snippet) you have this:
viewModel.messages = ko.dependentObservable(function () {
...
data: ko.toJSON(viewModel),
...
If the call to ko.toJSON tries to get the value of all the observable properties on the view model, it will try to evaluate the viewModel.messages property. That will call ko.toJSON again, leading to an infinite loop.

Categories

Resources