How to work with Knockout Code from a JQuery library file - javascript

I know this is not quite possible or rather feasible. But , this is what I intend to do .
I have a JQuery Plugin named sumoselect (https://hemantnegi.github.io/jquery.sumoselect/) for multi select dropdown like feature which has a static link at the bottom.
addSumoSelect: function () {
$('#intGroup1').SumoSelect({ createNew: true, countList : true});
}
This createNew() is a function defined in sumoselect JQuery plugin file :
createNew: function () {
var O = this;
O.optDiv.append($('<ul><li><a class="ispicon ispicon_plus" href="#addInt" data-toggle="modal" data-target="#addSchedule" data-bind="click:$parent.addGroup" title="Create New"> Create New</a></li></ul>'));
}
But the problem is JQuery is not able to parse data-bind and parent syntaxes as I suppose they are native to Knockout JS.
So , the click event is not being fired.
What can I do to make it work ?
The addGroup function is defined in my Knockout JS file.
UPDATE
Should I try to do something like :
$("#intrusionGroup1").click(function (element) {
element.parentNode.addIntrusion();
});

One possible way of rendering an HTML content which is wrapped in a string is to use the html binding, but for that too, you will need to use another knockout binding (unfortunately). So, the easiest way of doing this that I can think of now is to use jQuery itself and register a click event for that list item. For that you will need to make sure to have unique selectors for the list items.

You'll have to create a custom binding that initializes the widget and injects the custom html. Because a binding's init method is executed during ko.applyBindings, it has the chance to inject child elements early enough for them to be data-bound.
A data-bind has an init method that is called once when ko.applyBindings happens, and an update method that is called whenever a linked observable changes.
It would look something like this:
ko.bindingHandlers.sumoselect = {
init: function(elem, valueAccessor) {
// Initialize the widget, something like:
var widget = $(element).sumoselect(/* options */);
// By now, the new HTML should be injected and data-bound
// after `init` returns
// I'm not sure if the widget has a dispose method, but
// it's important to know you'll have to take care of this
// yourself.
ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
// Something like:
// widget.destroy()
});
}
};
An example that shows you what won't work:
ko.applyBindings({
onClick: function() { console.log("CLICK"); }
});
document.querySelector(".myDiv").innerHTML += "<button data-bind='click: onClick'>click me</div>";
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<p>Test button:</p>
<div class="myDiv"></div>
An example that shows you what will work:
ko.bindingHandlers.injectButton = {
init: function(element) {
element.innerHTML += "<button data-bind='click: onClick'>click me</div>";
}
}
ko.applyBindings({
onClick: function() { console.log("CLICK"); }
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<p>Test button:</p>
<div class="myDiv" data-bind="injectButton"></div>

Related

How to test the Knockout.js click binding with Jasmine

I'm having a problem with a jasmine test together with a knockout-template:
The html is similar to this:
<body>
<my-widget params="value: $data></my-widget>
</body>
the widget has a click-binding:
<div class="my-widget">
<a id="#clickme" data-bind="click: doSomething">Click me</a>
</div>
the widget-javascript is like this:
ko.components.register('my-widget', {
viewModel : function(params) {
this.doSomething = function() {
// doing something
};
},
template: {
require: 'text!../../templates/my-widget.html'
}
});
All of this works perfectly in production, but in Jasmine/Jquery, triggering a click on $('#clickme') does not execute the doSomething.
The following is an excerpt from my jasmine test (It's been greatly simplified but should contain the essentials):
beforeEach(function (done) {
require(['specHelpers', 'knockout'],
function (specHelpers, knockout) {
specHelpers.loadFixtureIntoPage("page.html", "myPage"); // template and id to use
expect($('#myPage')).toExist();
done();
});
});
it("WILL NOT TRIGGER MY CLICK", function (done) {
ko.applyBindings(myPage.pageViewModel, $('#myPage'))[0]);
setTimeout(function() {
$('#clickme').click();
// doSomething is not called :(
done();
}, 300);
});
When console.logging the #clickme element I can see that it is present.
It seems that the click binding in the widget does not get applied properly. However, when I run the test in bdd and it's over and failed - I can manually click this element and doSomething does get called.
What am I doing wrong? As I said, running the actual application works perfectly. It just seems that jasmine cannot handle the click bindings properly - I don't have this problem with the regular click events that are set in the document.ready
You really shouldn't be testing button clicks like that - you can be more certain if you just call the function doSomething() directly. Same way that you don't test any internals of JQuery yourself.
If you really really want to test events on a fixture, have you tried just
$("#clickme").trigger('click');
Also, double check that fixture is inserted into DOM in when you debug the test (say via browser)

Can't attach jQuery Plugins that is inside {{#if}} in EmberJS

In my emberjs app. I have bunch of jquery plugins that I need to bind to various elements.
I am using this piece of code to initiate jquery plugins for elements.
App.ApplicationView = Ember.View.extend({
didInsertElement: function(){
window.appPluginsInit(); // all jquery plugin init
// chart client init
var chartClientSettings = {
serverUrl: config.ajaxUrl
};
this.$('.chart-client').chartClient(chartClientSettings);
}
});
This only works for element that are initially loaded to the page. But for example if an element is under {{#if}} it doesn't seem to be attached with plugins.
Works
<button class="chart-client">Show Chart</button>
Doesn't work
Considering the someVar is false on initial load.
{{#if someVar}}
<button class="chart-client">Show Chart</button>
{{/if}}
You could make your .chart-client element a component and init the plugin on the components didInsertElement.
Component template:
<script type="text/x-handlebars" id="components/chart-client">
Show Chart
</script>
Component:
App.ChartClientComponent = Ember.Component.extend({
classNames: ["chart-client"],
tagName: "button",
chartClientSettings = {
serverUrl: config.ajaxUrl
},
didInsertElement: function () {
this.$().chartClient(this.get("chartClientSettings"));
}
});
Application view:
{{#if someVar}}
{{chart-client}}
{{/if}}
JSBin example: http://emberjs.jsbin.com/wemujo/1/edit?html,css,js,output
The if is parsed after jQuery, delay the jQuery action:
setTimeout((function () {
this.$('.chart-client').chartClient(chartClientSettings);
}).bind(this), 1);
This should do the job.
The solution provided by #Pete TNT is the best but I see 2 other options:
1) instead of putting things in if block, you can probably just use css to show/hide the button. That way the ".chart-client" is always in the DOM tree and hence you can apply the plugin to the element.
<button {{bind-attr class=":chart-client someVar:show:hide"}}>Show Chart</button>
2) Add an observer(also as suggested by #Pete TNT) : http://emberjs.jsbin.com/jekuvixufo/1/
(I would not have posted this but I was already writing a jsbin so I thought why waste my effort.)

Combination of $(document).ready and $scope.$on('$viewContentLoaded', function() {...})

I'm using JQuery UI components in a view of AngularJS/JQuery application.
I need something like this (does not work) in my JavaScript code:
$(document).ready(function () {
var elem = $('div[ng-view]')[0];
var $scope = angular.element(elem).scope();
$scope.$on('$viewContentLoaded', function() {
// multiple JQuery statements
// to support JQuery-UI componenets
});
});
This code is included as <script> into index.html that has <div class="container" ng-view> element.
My thinking was that we need a two-step process:
First JQuery reacts on document-ready HTML event and attaches a listener to Angular's $viewContenLoaded using $scope retrieved using [ng-view] element.
Then each time a view is loaded my JQuery code will be executed and JQuery UI components get activated and wired.
Apparently my logic is flawed somewhere. Please point me in the right direction.
ADDITIONAL INFO (posted 03/31/14):
The rest of my code (controllers, service, routing) is written in TypeScript.
That element needs to be compiled in order to bind angulars scope to that element. You could try something like:
var scope = angular.injector(['ng']).get('$rootScope').$new();
var compile = angular.injector(['ng']).get('$compile');
compile(elem)(scope);
scope.$on('$viewContentLoaded', function(){
// Your code
});
Though I would suggest putting your code in a directive. The code I shown above is nothing more than a hack and dangerous since now you have global access to your services.

Knockout firing click binding on applyBindings

Recently I've separated out ViewModel to a separate JavaScript file.
var Report = (function($) {
var initialData = [];
var viewModel = {
reports: ko.observableArray(initialData),
preview: function(path) {
// preview report
},
otherFunctions: function() {}
};
return viewModel;
})(jQuery);​
Here is the HTML and Knockout related code
<script type="text/javascript" src="path/to/report/javascript"></script>
<script type="text/javascript">
$(document).ready(function () {
ko.applyBindings(Report, document.body);
});
</script>
HTML user interface has a button on which click is data bind to preview function in the view model
<input type="button" name="Preview" id="Preview" class="btnPreview"
data-bind="click: Report.preview('url/to/report')" />
Problem preview method is called when the following line execute in $(document).ready() function
ko.applyBindings(Report, document.body);
That is without user clicking on the Preview button preview function is fired. What could be the reason for this behavior? The whole stuff was working fine when I'd view model JavaScript in the HTML page itself.
The reason is, that you're indeed invoking the preview function (because writing functionName means referring to the function, writing functionName() means calling it).
So data-bind="click: Report.preview" would be working as expected, but without handing over the parameter.
As the manual states (on a different topic, but this still applies):
If you need to pass more parameters, one way to do it is by wrapping your handler in a function literal that takes in a parameter, as in this example:
<button data-bind="click: function(data, event) { myFunction(data, event, 'param1', 'param2') }">
Click me
</button>
or in your case:
data-bind="click: function() { Report.preview('url/to/report') }"
Another solution would be to make preview() return a function (pretty much the same thing actually):
preview: function(path) {
return function() {
// ...
}
}
Another solution is to use 'bind' construct:
data-bind="click: Report.preview.bind($data, 'url/to/report')"
where the first parameter to bind() will become the 'this' in the called function.

Backbone :: Using jQuery Plugins on Views

I'm having trouble figuring out a clean way to do this. Let's take for an example a code snippet from the example todo app that comes with backbone:
addOne: function(todo) {
var view = new TodoView({model: todo});
$("#todo-list").append(view.render().el);
},
So the ToDo view is being rendered and then it's being appended to #todo-list. But let's suppose we want to add a jQuery plugin to ToDo view. Where should we place the $(".todo").plugin() snippet? If we place it inside the ToDo view render function the HTML element is not set on the page, so the plugin won't 'latch' to any DOM element. If we place this inside the addOne function it will look ugly.
So, what's the best way?
The answer largely depends on the plugin you're talking about.
For example, most of the jQueryUI controls and the KendoUI controls allow you to call the plugin method from the render of the view, directly, before the HTML is attached to the DOM. You simply call it on the view's el.
For example, if I wanted to use KendoUI's menu on a view that generated:
Backbone.View.extend({
tagName: "ul",
render: function(){
var html = "<li>foo</li><li>bar</li>";
this.$el.html(html);
this.$el.kendoMenu();
}
});
There are some plugins that require the HTML to be a part of the DOM already, for whatever reason. In this case, I typically provide an onShow function in my views, and have the code that is displaying the view check for this and call it if it exists.
For example, there's a "Layout" plugin that I've used a few times. This plugin requires the HTML to be part of the DOM before it can work:
MyView = Backbone.View.extend({
render: function(){
var html = "generate some html here...";
this.$el.html(html);
},
onShow: function(){
this.$el.layout();
}
});
// .... some other place where the view is used
var view = new MyView();
view.render();
$("#someElement").html(view.el);
if (view.onShow) {
view.onShow();
}
FWIW: I've written the code for onShow and other common methods and events dozens of times, and have consolidated it all into a Backbone add-on called Backbone.Marionette, so that I don't have to write it anymore.
http://github.com/derickbailey/backbone.marionette
There's a lot more than just this available in this add-on, of course.
You can use the backbone $ method like so this.$('todo') to use context scoped jquery querying which will allow you to search in the current view DOM fragment which wasn't added to the document DOM tree yet.
From my experience adding jquery plugin binding in either render method or some kind of helper function if there was more custom bindings which would be then called from render method after the template was created.

Categories