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.)
Related
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>
I have created an ember.js template (about.hbs). I want to run some jQuery on the page but the jQuery only works if I reload the page.
I don't really get where I am meant to put the jQuery code to make it run when the template is rendered. At them moment I have put it in the controller.
App.AboutController = Ember.ObjectController.extend({
$(document).ready(function() {
//my jquery code
});
});
Could someone help me out?
You are going to want to lifecycle callbacks (see Ember docs). When a template is rendered Ember will trigger the didInsertElement callback. You can simply add your own code to this function, and if you want to use any jQuery inside of that callback you must keep the scope in mind: this.$()
Your code will look like this:
didInsertElement: function(){
this.$() //jquery code
}
Here are my routes:
ExtractCreator.Router.map(function () {
this.route('filter', { path: '/' }, function () {
this.route('geog_levels');
});
});
Here's my view which creates the jquery ui popup:
ExtractCreator.FilterGeogLevelsView = Ember.View.extend({
didInsertElement : function(){
var controller = this.controller;
$('#filter-dialog').dialog({
modal:true,
stack: false,
title: "Geogography Levels Filter",
close: function(e,ui) {
controller.transitionToRoute('filter');
},
}).dialog("moveToTop");
}
});
And the template:
{{#each geog_level_group in model}}
<h3>{{geog_level_group.label}}</h3>
{{#each geog_level_filter in geog_level_group.geog_level_filters}}
<div {{bind-attr class="geog_level_filter.disabled?:disabled"}}>
<label>{{geog_level_filter.label}} - {{geog_level_filter.id}}</label>
{{input type="text" value=geog_level_filter.label }}
</div>
{{/each}}
{{/each}}
I'm binding the inputs to my model in the template, but whenever I type anything in the input field it doesn't update anywhere else on the page, nor does it show as updated in my (chrome) ember inspector's view of the model.
If I change the value manually from the ember inspector, then it updates inside the popup correctly.
If I take it out of the popup (or just remove the popup code), then everything binds correctly and the label will update when I change the input value.
How do I get correct binding behavior to work from inside the dialog?
Well i faced once the same kind of strange behaviors and the problem is mayBe due to the didInsertElement with child views. (yes you some how have child views as you use an each and an ember {{input}} which is a view aswell).
One way which may solve your problem is tu run your modal after ember has finished to render everything in the queue. You may achieve this by modifying your code as follow :
didInsertElement : function(){
var controller = this.controller;
Ember.run.scheduleOnce('afterRender', this ,function(){
$('#filter-dialog').dialog({
modal:true,
stack: false,
title: "Geogography Levels Filter",
close: function(e,ui) {
controller.transitionToRoute('filter');
},}).dialog("moveToTop");
});
}
It turns out that the reason the binding wasn't occurring is that the jQuery-ui dialog function was appending the "#filter-dialog" element to the the body tag, but on creation of my Ember app I used the "rootElement" option to limit the scope of Ember to another div with id of "extract-creator".
My application creation code:
ExtractCreator = Ember.Application.create({
rootElement: '#extract-creator'
});
Using the appendTo option when creating the dialog, I can keep it within the scope of the Ember application:
ExtractCreator.FilterGeogLevelsView = Ember.View.extend({
didInsertElement : function(){
var controller = this.controller;
$('#filter-dialog').dialog({
modal:true,
stack: false,
title: "Geogography Levels Filter",
appendTo: "#extract-creator",
close: function(e,ui) {
controller.transitionToRoute('filter');
},
}).dialog("moveToTop");
}
});
An alternative solution would have been to remove the "rootElement" line from the Ember create option hash, making it default to the body tag.
Special thanks to Buck Doyle for posting a working JSBin in the comments.
I'm trying to use this lazy loading plugin in a backbone app, I adapted the template to work with the plugin and called and the plugin script in but it doesn't work. Here is the jsfiddle:http://jsfiddle.net/swayziak/9R9zU/27/
The template:
<script id="bookTemplate" type="text/template">
<img src="https://www.amrms.com/ssl/iftafoundation/bluepay/Images/Loading_Animation.gif" data-src="<%= image %>"/>
<h2 class="bookTitle"><%= title %><h2>
</script>
And the script on document ready:
$(document).ready(function() {
$("img").unveil();
});
I don't know what's wrong with my code.
First of all you are using incorrect path to the plugin in your fiddle.
Another one thing why it is not working is related to the place where you put plugin initialization. You initialize it on the document ready but your images is not rendered yet. So you have to initialize plugin after rendering the view (when images are added to the DOM).
You have a problem with version of Zepto, unveil uses filter function that was updated in zepto latest version: (from http://zeptojs.com/#changelog)
New features
$.fn.filter(function(index){ ... })
Check out updated jsfiddle (http://jsfiddle.net/9R9zU/41/) that uses http://zeptojs.com/zepto.js source.
EDIT:
to make the entire example work I moved the unveil call to the view render function (http://jsfiddle.net/9R9zU/52/)
app.BookView = Backbone.View.extend ({
tagName: 'div',
className: 'book',
template: _.template( $( '#bookTemplate' ).html()),
render: function() {
this.$el.html(this.template(this.model.toJSON()));
this.$el.find("img").unveil();//for every render we run the lazy loader
return this;
}
});
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.