I have a side view list and when one of the item in the list is clicked I show the corresponding view
My code is as follows:
VIEW
app.View.BrandSidePanelView = Backbone.View.extend({
tagName: 'div',
template: _.template($('#brand-side-panel').html()),
template_brand: _.template($('#brand-create').html()),
template_offer: _.template($('#offer-create').html()),
initialize: function() {
this.render();
},
events: {
'click .bra-main': 'showBrandCreateView',
'click .bra-off': 'showOfferCreate',
'click .bra-cmgn': 'showCampaignCreate'
},
showBrandCreateView: function(e) {
e.preventDefault();
this.reset();
$('.crt-cnt').html(this.template_brand());
},
showOfferCreate: function(e){
e.preventDefault();
this.reset();
$('.crt-cnt').html(this.template_offer());
},
render: function() {
$('.crt-cnt').html(this.template_brand());
var $el = $(this.el);
$el.html(this.template());
return $(this.el);
},
reset: function(){
$('.crt-cnt').empty();
}
});
HTML
<div class="list-group">
<div class="list-group-item bra-main"><i class="fa fa-angle-double-right"></i> <fmt:message key="brand" /></div>
<div class="list-group-item bra-off"><i class="fa fa-angle-double-right"></i> <fmt:message key="brand.offer" /></div>
<div class="list-group-item bra-cmgn"><i class="fa fa-angle-double-right"></i> <fmt:message key="brand.campaign" /></div>
</div>
Everything here is static. Nothing is fetched from the server. I am very new to backbone. The current code works but wants to know if I am doing it the right way.
If your side view list won't change in time, it's ok to do it like this, it's just a sort of static menu.
But if the elements in the list could change, you must wrap it in a View and fetch the elements from the server
Related
I have a controller which is populating content to content areas using ng-repeat. The issue is that some of this content needs to come front template files and so needs to be compiled 'on the fly'. Right now I have this function dynamically adding content:
$scope.layouts = [
{ id: 'Dashboard', icon: 'dashboard', view: '/qph/views/Dashboard.php' },
{ id: 'Customers', icon: 'people', view: '/qph/views/users.php' },
{ id: 'Quotes', icon: 'format_list_bulleted', view: '/qph/views/Quotes.php' }
];
$scope.workspace = {};
var getTemplate = function(id){
var view = 'test.php';
$timeout(function() { //added timeout
if($templateCache.get(view) === undefined) {
$templateRequest(view).then(function (data) {
$scope.workspaces.forEach(function (v) {
if (v.id == id) v.content = $compile(data)($scope);
});
});
} else {
$scope.workspaces.forEach(function (v) {
if (v.id == id) v.content = $compile($templateCache.get(view))($scope);
});
}
}, 2000);
};
$scope.workspaces =
[
{ id: 1, name: "Dashboard", icon: 'dashboard', active:true }
];
getTemplate(1);
I have checked that the data variable has the html content as expected, but the compile is outputting the following:
{"0":{"jQuery331075208394539601512":{"$scope":"$SCOPE","$ngControllerController":{}}},"length":1}
Does anyone know why its not compiling the html content as expected?
Here is the template content for reference:
<div class="col-sm-6 col-sm-offset-3" ng-controller="UserController">
<div class="col-sm-6 col-sm-offset-3">
<div class="well">
<h3>Users</h3>
<button class="btn btn-primary" style="margin-bottom: 10px" ng-click="user.getUsers()">Get Users!</button>
<ul class="list-group" ng-if="user.users">
<li class="list-group-item" ng-repeat="user in user.users">
<h4>{{user.name}}</h4>
<h5>{{user.email}}</h5>
</li>
</ul>
<div class="alert alert-danger" ng-if="user.error">
<strong>There was an error: </strong> {{user.error.error}}
<br>Please go back and login again
</div>
</div>
</div>
</div>
Here is the tabs view that is to display the compiled content:
<ul class="nav nav-tabs workspace-tabs">
<li class="nav-item" ng-repeat="space in workspaces">
<a class="nav-link" data-toggle="tab" href="#workspace{{space.id}}" ng-class="(space.active == true ) ? 'active show': ''">
<span class="hidden-sm-up"><i class="material-icons md-24">{{space.icon}}</i></span>
<span class="hidden-xs-down">{{space.name}}</span>
<button ng-click="workspace.remove($index)">x</button>
</a>
</li>
</ul>
<div class="tab-content workspace-content">
<div ng-repeat="space in workspaces" id="workspace{{space.id}}" class="tab-pane fade in" ng-class="(space.active == true ) ? 'active show': ''">
{{space.content}}
</div>
</div>
The $compile service creates a jqLite object that needs to be added to the DOM with a jqLite or jQuery append() method. Using interpolation {{ }} will only render the stringified value of the jqLite object.
<div class="tab-content workspace-content">
<div ng-repeat="space in workspaces" id="workspace{{space.id}}" class="tab-pane fade in" ng-class="(space.active == true ) ? 'active show': ''">
̶{̶{̶s̶p̶a̶c̶e̶.̶c̶o̶n̶t̶e̶n̶t̶}̶}̶
<compile html="space.html"></compile>
</div>
</div>
Instead, use a custom directive to compile and append the HTML data to the DOM:
app.directive("compile", function($compile) {
return {
link: postLink,
};
function postLink(scope, elem, attrs) {
var rawHTML = scope.$eval(attrs.html)
var linkFn = $compile(rawHTML);
var $html = linkFn(scope);
elem.append($html);
}
})
For more information, see AngularJS Developer Guide - HTML Compiler.
Use a directive.
app.directive('myCustomer', function() {
return {
templateUrl: 'test.php',
controller: 'UserController'
};
})
Template cache will be managed automatically.
The a href with k-add-button in the viewtemplate works only on times, not multible times. Have anybody a idea why, or mybe a sample or solution that's work correctly?
If the k-add-button is outside from the template, it works fine.
<div id="example"></div>
<script type="text/x-kendo-template" id="viewtemplate">
<div class='k-widget'>
<span>Filter:</span><span>#:filtertext#</span><span>Filterwert:</span><span>#:filterwert#</span>
<a class="k-button k-edit-button" ><span class="k-icon k-edit"></span></a>
<a class="k-button k-delete-button" ><span class="k-icon k-delete"></span></a>
<a class="k-button k-add-button" ><span class="k-icon k-add"></span></a>
</div>
</script>
<script type="text/x-kendo-template" id="editTemplate">
<div class='k-widget'>
<input type="text" class="k-textbox" data-bind="value:filtertext" name="filtertext" required="required" validationMessage="required" />
<span data-for="filtertext" class="k-invalid-msg"></span>
<input type="text" class="k-textbox" data-bind="value:filterwert" name="filterwert" required="required" validationMessage="required" />
<span data-for="filterwert" class="k-invalid-msg"></span>
<a class="k-button k-update-button" ><span class="k-icon k-update"></span></a>
<a class="k-button k-cancel-button" ><span class="k-icon k-cancel"></span></a>
</div>
</script>
<script type="text/javascript">
$(document).ready(function () {
var dataSource = new kendo.data.DataSource({
data: [ { filternr: 0, filtertext: "SA-Code", filterwert:"123"} ],
schema: {
model: {
id: "filternr",
fields: {
filternr: { type: "number" },
filtertext: { type: "string" },
filterwert: { type: "string" }
}
}
}
});
var listView = $("#example").kendoListView({
dataSource: dataSource,
template: kendo.template($("#viewtemplate").html()),
editTemplate: kendo.template($("#editTemplate").html()),
}).data("kendoListView");
$(".k-add-button").click(function(e) {
listView.add();
e.preventDefault();
});
}); // Ende $(document).ready()
</script>
I have modified your code slightly at this dojo: list view template with button
You will hopefully notice the following changes I have made.
I have added a DataBound event to your listview object like so:
editTemplate: kendo.template($("#editTemplate").html()),
dataBound: onDataBound
I have then wrapped your button click event in the appropriate function called onDataBound
The reason the button is not working correctly is that once the data you have added to the dataSource is saved and then re-read the template is re-rendered and this then removes the event handlers from the buttons that you associated then with. In order to re-link them we reattach it as part of the dataBound event and then everything is working happily.
If you need more info let me know.
Hopefully the example shows what it is doing.
I'm having issues rendering a modal popup dialog as a template with Underscore and Backbone on a Django website, otherwise it works as a standalone single page app. Why would that be?
Here is a snippet of the template:
<script type="text/template" id="popup-template">
<div class="modal sector-popup" style="background-color:#f5f5f5; visibility:visible;">
<div class="modal-header" style="background-color:#FFFFFF">
<input type="image" class="pull-right" id="closebtn" src="../media/x-button.png" data-dismiss="modal">
...
</div>
</script>
And here is where it is used in the website:
window.FormPopup = Backbone.View.extend({
template: _.template($('#popup-template').html()),
events: {
"dblclick .sector-label" : "edit",
"keypress .sector-label-edit input" : "setLabel"
},
render: function() {
var view = this;
this.$el.html(this.template(this.model.toJSON()));
...
},
});
var LandingView = Backbone.View.extend({
initialize: function() {
console.log('Landing View has been initialized');
this.render();
},
template: Handlebars.compile($('#landingPage').html()),
render: function() {
this.$el.html(this.template);
},
events: {
// I want to render the subview on click
'click .btn-login' : 'renderlogin',
},
renderlogin: function() {
// Is this the right way to instantiate a subview?
var loginpage = new LoginView({ el: $('#modal-content') });
}
});
And my next view, which basically just empties the $('#modal-content') element...
var LoginView = Backbone.View.extend({
initialize: function() {
this.render();
console.log("login view initialized");
},
template: Handlebars.compile($('#loginPage').html()),
render: function() {
this.delegateEvents();
this.$el.html(this.template);
},
events: {
// this is where things get super confusing...
// Upon clicking, LoginView gets re-initialized and
// subsequent clicks are called for each number of times
// the view is initialized.
'click .js-btn-login' : 'login'
},
login: function(e) {
e.preventDefault();
var self = this;
console.log($(this.el).find('#userSignIn #userEmail').val());
console.log($(this.el).find('#userSignIn #userPassword').val());
}
});
My templates:
LANDING PAGE:
<script type="text/x-handlebars-template" id="landingPage">
<div>
<div class="auth-wrapper">
<div class="logo">
<img src="img/login/logo-landing.png"/>
</div>
<div class="to-auth-buttons-wrapper">
<a class="btn-to-auth btn-signup" href="#">Sign Up</a>
<a class="btn-to-auth btn-login" href="#">Log In</a>
</div>
</div>
</div>
</script>
LOGINPAGE:
<script type="text/x-handlebars-template" id="loginPage">
<div>
<div class="header">
Back
</div>
<div class="auth-wrapper">
<div class="logo">
<img src="img/login/logo-landing.png"/>
</div>
<form method="post" id="userSignIn">
<input class="form-control input-signin" type="text" name="useremail" placeholder="Email" id="userEmail" value="tester">
<input class="form-control input-signin" type="password" name="userpass" placeholder="Password" id="userPassword">
<button class="btn-to-auth btn-login js-btn-login">Log In</button>
</form>
</div>
</div>
</script>
My goal:
From within LandingView, upon clicking .btn-login, render LoginView.
From within LoginView, upon clicking .js-btn-login, console.log
contents of form
Problems:
In LoginView, upon clicking .js-btn-login, I see that the initialize function is called again.
In LoginView, I can't use jquery to get the values inside of $('#userSignIn #userEmail').val() and $('#userSignIn #userEmail').val() because they aren't there on render. I see the initial hardcoded value ( input[value="tester"]) but this is all it sees.
My question:
How do I get the view to stop reinitializing on an event firing and how do I get the values in my DOM after rendering?
I'm building a wizard widget with Durandal, and I'd like to use it like so:
<div data-bind="wizard: options">
<!-- Step 1 -->
<span data-part="step-header-1">
Step 1
</span>
<div data-part="step-content-1">
step content here
</div>
<!-- Step 2 -->
<span data-part="step-header-2">
Step 2
</span>
<div data-part="step-content-2">
step content here
</div>
</div>
This is the actual widget (cut down for brevity):
<div class="wizard-container">
<ul class="steps" data-bind="foreach: steps">
<li>
<span data-bind="html: heading"></span>
</li>
</ul>
<!-- ko foreach: steps -->
<div class="wizard-step" data-bind="css: { active: isActive }">
<div data-bind="html: content">
</div>
</div>
<!-- /ko -->
</div>
I've sort of gotten it working, using jQuery to grab the data-parts, assign the data-part's inner HTML to a property on my step model, and then use the html-binding to bind the content to each step. This works on the DOM side of things, but doing it this way means that my step content won't get data-bound.. I am pretty sure it's because I use the html binding, which does not bind the content.
Is there a way to do this with Durandal widgets, without separating each step into a new view?
Here's an implementation that uses a traditional Durandal master/detail approach in combination with a Tab widget. The tab widget only implements the tabbing functionality, while the Master controls what's pushed into it and the Detail controls the behavior/layout of itself.
Master
Viewmodel
define(['./tab', 'plugins/widget', 'knockout'], function (Tab, widget, ko) {
return {
tabs: ko.observableArray([
new Tab('Durandal', 'A ...', true),
new Tab('UnityDatabinding', 'A ...'),
new Tab('Caliburn.Micro', 'C ...')
]),
addNewTab: function() {
this.tabs.push(new Tab('New Tab ', 'A test tab.'));
}
};
});
View
<div>
<h1>Tabs sample</h1>
<!-- ko widget : {kind: 'tabs', items : tabs} -->
<!-- /ko -->
<button class="btn" data-bind="click: addNewTab">Add</button>
</div>
Detail
Viewmodel
define(['durandal/events', 'knockout'], function(events, ko) {
return function(name, content, isActive) {
this.isActive = ko.observable(isActive || false);
this.name = name;
this.content = content;
};
});
view
<div>
<div data-bind="html: description"></div>
</div>
Tab widget
Viewmodel
define(['durandal/composition', 'jquery'], function(composition, $) {
var ctor = function() { };
ctor.prototype.activate = function(settings) {
this.settings = settings;
};
ctor.prototype.detached = function() {
console.log('bootstrap/widget/viewmodel: detached', arguments, this);
};
ctor.prototype.toggle = function(model, event){
this.deactivateAll();
model.isActive(true);
};
ctor.prototype.deactivateAll = function(){
$.each(this.settings.items(), function(idx, tab){
tab.isActive(false);
});
};
return ctor;
});
View
<div class="tabs">
<ul class="nav nav-tabs" data-bind="foreach: { data: settings.items }">
<li data-bind="css: {active: isActive}">
<a data-bind="text: name, click: $parent.toggle.bind($parent)"></a>
</li>
</ul>
<div class="tab-content" data-bind="foreach: { data: settings.items}">
<div class="tab-pane" data-bind="html: content, css: {active: isActive}"></div>
</div>
</div>
Live version available at: http://dfiddle.github.io/dFiddle-2.0/#extras/default. Feel free to fork.
As I suspected, the problem with my bindings not applying, was due to the fact that I used the html binding to set the step content. When Knockout sets the HTML, it does not apply bindings to it.
I wrote my own HTML binding handler, that wraps the HTML and inserts it as a DOM-node - Knockout will hapily apply bindings to this.
(function(window, $, ko) {
var setHtml = function (element, valueAccessor) {
var $elem = $(element);
var unwrapped = ko.utils.unwrapObservable(valueAccessor());
var $content = $(unwrapped);
$elem.children().remove().end().append($content);
};
ko.bindingHandlers.htmlAsDom = {
init: setHtml,
update: setHtml
};
}(window, jQuery, ko));
Please note, this only works when the binding value is wrapped as a node - e.g within a div tag. If not, it won't render it.