I'm developing a web community in CakePHP and started to have doubts about using jQuery to provide useful widgets around the views when needed.
For example, I've wrote a jQuery plugin which searches the data inside specific input text, asks to my database and get the results handled with events.
Basically the plugin is perfect for simple applications but it's basically useless for a community where I use it almost in all the views and every time handling it with different events and methods, making it with huge event declarations and very annoying to be debugged.
I thougt to solve the problem by using default events from the widget and add the possibility set additional events specific for the view, but how can i do that?
this is the situation i thought
The Green area of the image is where I'm not sure, where I should put the default events to be retrived every time i need them? After known that then, in the view, I could add some event to the widget to be more easy to use.
For widget I intend every kind of html portion is loaded via javascript and is interactive, maybe an input search which retrieves a list of results or something like that.
My question is how can I set default events in runtime to the widget without copy and paste every time?
And my second question is, how can I add to them specific events for the view only?
Some tutorial somewhere online would be also nice.
My answer requires backbone.js so I am not sure if this will help you.
You could separate your widget into a wrapper and the real widget.
The wrapper could handle events like your close event:
var WidgetWrapper = Backbone.View.extend({
tagName: 'div',
// This should be a mustache template:
template: '<a class="close" href="#">close</a><div class="content"></div>',
events: {
'.close click': 'close',
'.open click' : 'open'
},
close: {
this.$el.hide();
},
open: {
alert('I am open');
}
render: {
this.$el.html(Mustache.to_html(view.template, view.model.toJSON());
}
});
The real widget could render itself inside the wrapper widget and both views could interact with the data model (this.model).
var SpecialWidget = Backbone.View.extend({
tagName: 'div',
// This should also be a mustache template:
template: '<input> open',
events: {
'input change': 'edit'
},
render: function() {
if(!this.wrapper) {
this.wrapper = new WidgetWrapper();
}
// Hand over the model to the wrapper
this.wrapper = this.model;
// Render the wrapper
this.wrapper.render();
// Insert the widget content inside the wrapper
this.$el.empty().append(this.wrapper.$el);
this.$(".content").html(Mustache.to_html(view.template, view.model.toJSON());
},
edit: function() {
alert("Changed");
},
});
This would allow you to separate your events.
You could also do it the other way round and use a wrapper with a sub view.
Related
After creating and rendering a Kendo UI TreeView to fill a DIV, repeat invocation alternately renders only "loading..." or works properly. Since I am having possibly similar problems with Kendo UI ContextMenu, I speculate there may be some required cleanup in between, which is passively done by even invocations such that odd invocations work, but I can't figure it out (a link to Kendo UI docs I might be missing so I can understand why I've missed this would be appreciated to help with other issues).
In my JSFiddle example, click "draw" over and over and you'll see the alternate behavior. Speculatively clicking "draw, destroy, draw, destroy..." does not seem to help.
https://jsfiddle.net/rk3nfnnu/
<script>
function TreeDestroy() { // http://stackoverflow.com/questions/5431351
$('#Tree_Space').data('kendoTreeView').destroy();
alert('destroyed');
}
function TreeShow() {
$('#Tree_Space').kendoTreeView({
dataSource: [ { Name: 'Top', items: [ { Name:'Item' } ] } ],
template: kendo.template($('#Tree_template').html())
});
alert('shown');
}
</script>
draw |
destroy
<div id='Tree_Space'>
</div>
<script type='text/x-kendo-template' id='Tree_template'>
#= item.Name#
</script>
I have updated that fiddle. The destroy(); method probably only destroys allocated dom elements after the widget was rendered (the nodes). I doubt it cleans up the wrappers and whatnot. In your TreeDestroy(), issue a clear on that element div. Of course, you should call TreeDestroy prior to TreeCreate just in case.
function TreeDestroy() { // http://stackoverflow.com/questions/5431351
$('#Tree_Space').data('kendoTreeView').destroy();
$('#Tree_Space').html('');
alert('destroyed');
}
Here is some kendoui documentation that refers to how to handle manual deletion of widgets.
I am designing a custom widget using DIJIT and DOJO 1.10.
Basically my custom widget needs to have some behavior like a button, so when user click on it something can happen. I need to make sure other developers can add custom code when onClick it is fired on that widget.
After reading this guide I understood that my custom widget should implement extension points. I have notice in the source code in DIJIT for Button.js and I see they using a special mixin called dijit._OnDijitClickMixin.
Below code for my widget, so far it works fine, but I would like to know:
Is extension point the right way? Does a better alternative exists?
Reading at the documentation I see the following code.
_onButtonClick: function( /*Event*/ e){
... // Trust me, _onClick calls this._onClick
},
_onClick: function( /*Event*/ e){
...
return this.onClick(e);
},
onClick: { // nothing here: the extension point!
}
My custom widget does not implement any of these functions and seems working fine.
Shall I include these functions? What is the reason for that?
Widget
define([
'dojo/_base/declare',
'dijit/_WidgetBase',
'dijit/_OnDijitClickMixin',
'dijit/_TemplatedMixin',
'dojo/text!./templates/template.html'
], function (
declare,
_WidgetBase,
_OnDijitClickMixin,
_TemplatedMixin,
template
) {
return declare([_WidgetBase, _TemplatedMixin, _OnDijitClickMixin], {
templateString: template
});
});
HTML template
<div data-dojo-attach-event="ondijitclick:onClick"> </div>
Initialize the widget
this._iconPage = new IconPages({
id: 'iconPage',
onClick: function () {
//do smt
}.bind(this)
}).placeAt('content');
What you have is fine as the onClick method is meant to be overwritten to hook into events. What you can also do is hook into your IconPages "click" event using dojo/on by doing something like this:
on(this._iconPage, "click", /*function here*/);
I'm not sure why I can't get the button element using my UI hash. This is what my Layout looks like:
Layout: App.Base.Objects.BaseLayout.extend({
// Rest of the code left out for brevity
ui: {
btnSave: "#btnSave"
},
events: {
"click #ui.btnSave": "onSave"
},
onInitialize: function () {
this.listenTo(App.vent, "DisableSaveButton", function(val) {
this.disableSaveButton(val);
},this);
},
disableSaveButton: function () {
this.ui.btnSave.prop("disabled",val).toggleClass("ui-state-disabled",val);
},
onSave: function () {
alert("saved!");
}
})
In VS2013, when my breakpoint hits the line inside disableSaveButton method, I entered $("#btnSave") into the Watch window and I was able to get the element back. I could tell because it had a length of 1. From this, I know the button is rendered. However, if I enter this.ui.btnSave into the Watch window, I would get an element with length of 0.
My BaseLayout object is basically a custom object extended from Marionette.Layout
Marionette version: 1.8.8
Any ideas why I can't find the button element using this.ui.btnSave?
Thanks in advance!
Got some help from a coworker and the issue might be because the element is out of scope. Basically, inside the Layout object, 'this' does not contain the element. We were able replace 'this.ui.btnSave' with '$("#btnSave",this.buttonset.el)' and that works fine. buttonset is the region that actually contains the html element.
This seems like an inconsistency because even though the ui hash didn't work, the click event utilizing the ui hash did work.
UPDATE 6/3/2015:
Another coworker of mine provided a better solution. Basically, in my Layout I use a display function to display my view. It looks something like this:
Layout: App.Base.Objects.BaseLayout.extend({
// Rest of the code left out for brevity
display: function() {
$(this.buttonset.el).html(_.template($("#buttonset-view").html(), {"viewType": viewType}));
}
})
Basically, I'm saying to set the html of my region, which is this.buttonset.el, to my template's html. As of now, my layout doesn't know any of the elements inside the region. It just contains a region which displays the elements. So there is some sort of disconnect between my layout and the elements in my region.
The correct solution, as opposed to my earlier workaround, is to simply add the following line of code at the end:
this.bindUIElements();
From Marionette Annotated Source:
This method binds the elements specified in the “ui” hash inside the
view’s code with the associated jQuery selectors.
So this final code looks like this:
Layout: App.Base.Objects.BaseLayout.extend({
// Rest of the code left out for brevity
display: function() {
$(this.buttonset.el).html(_.template($("#buttonset-view").html(), {"viewType": viewType}));
this.bindUIElements();
}
})
With this, I was able to finally able to retrieve my element using this.ui.btnSave.
I've been following a tutorial about using backbone and jqm altogether which consists of disabling the jqm router and using backbone's one instead, but i frankly dislike it's routing approach :
var AppRouter = Backbone.Router.extend({
routes:{
"":"home",
"page1":"page1"
},
initialize:function () {
// Handle back button throughout the application
$("body").on('click', '.back', function(event) {
window.history.back();
return false;
});
},
home:function () {
console.log('#home');
this.changePage(new HomeView());
},
page1:function () {
console.log('#page1');
this.changePage(new Page1View());
}
changePage:function (page) {
$(page.el).attr('data-role', 'page');
page.render();
$('body').append($(page.el));
$.mobile.changePage($(page.el), {changeHash:false, transition: $.mobile.defaultPageTransition, allowSamePageTransition:true});allowSamePageTransition:true});
What it does is that upon each hash change, it calls changePage which does a view creation, a template rendering, a div creation by appending it to the body, and a changePage to this new element.
Now, this means creating a div on every page change
. Also, views aren't changing everytime you change page, but only when models change, and the "render" should be fired upon model change, and the rendered view will be stored in view.el.
What is done here is calling the render at every page show, even if the page is still the same.
How can I tell jqm to show the page.el with a transition, without having to do a or appending a new div to the body everytime ?
I thought of storing my views in an array or a collection and telling the router to pull them from there, and then show it by appending a "temp div" or something, but there must be a better way.
Does anyone ever faced the integration of Etch.js in a Backbone.Marionette.js application?
I'm having issues binding the save event. This is the code for my Marionette view:
MyApp.module('Views', function(Views, App, Backbone, Marionette, $, _) {
Views.DetailsView = Marionette.ItemView.extend({
template: '#details',
initialize: function(options) {
_.bindAll(this.model, 'save'); // I think the problem is related to the binding
this.model.bind('save', this.model.save);
},
events: {
'mousedown .editable': 'editableClick'
},
editableClick: etch.editableInit
});
});
and in my template I have something like the following:
<div id="detail-expanded">
<p>Description: <span class="editable">{{ description }}</span></p>
</div>
The plugin is loaded correctly, if I click on the field I can see the Etch buttons bar, I can edit the content of the element made editable and if I click on the save button I'm actually able to trigger the model save() method.
The problem is that the model submitted is the original one, without the edits that I did to the field. I think it's a binding problem, any ideas?
Thanks in advance, as always.
So, the problem here is not really related to marionette, it's that etch doesn't handle moving the data from the editable field to the model. I should be more explicit about that in the docs. What you want to do is create a save function on the view that does this for you like so:
Views.DetailsView = Marionette.ItemView.extend({
template: '#details',
initialize: function(options) {
_.bindAll(this, 'save');
this.model.bind('save', this.save);
},
events: {
'mousedown .editable': 'editableClick'
},
editableClick: etch.editableInit,
save: function() {
// populate model attrs from dom
var title = this.$('.title').text();
var body = this.$('.body').text();
this.model.save({title: title, body: body});
}
});
Sorry for the confusion. I can see how the docs are misleading in this regard.
I believe what you want is
_.bindAll(this, 'save');
instead of
_.bindAll(this.model, 'save');