I'm using:
1.Adapter:
rivets.adapters[':'] = {
subscribe: function(obj, keypath, callback) {
obj.on('change:' + keypath, callback)
},
unsubscribe: function(obj, keypath, callback) {
obj.off('change:' + keypath, callback)
},
read: function(obj, keypath) {
return obj.get(keypath)
},
publish: function(obj, keypath, value) {
//console.log(obj);
obj.set(keypath, value);
}
}
2.Collection :
var FriendsList = Backbone.Collection.extend({
model: Friend,
initialize : function (Friend,options) {
this.addFriend();
},
addFriend : function () {
this.add ( this.f = new this.model() );
}
});
window.friends = new FriendsList();
rivets.bind($("#friendsTable tfoot"), {friends:window.friends})
3.Declaration
<tr class="totals">
<td colspan="2">
Add Friend</td>
<td class="b r">Total</td>
<td class="total r b"></td>
</tr>
When using . notation context of addFriend is set to target element - how it is possible to use : notation to force "this" to point to collection instance?
I am not sure why you what to do this or why your code is structure like this,
but if you really want to do it, you can configure rivets in that way.
Default rivets configuration:
rivets.configure({
// Attribute prefix in templates
prefix: 'rv',
// Preload templates with initial data on bind
preloadData: true,
// Root sightglass interface for keypaths
rootInterface: '.',
// Template delimiters for text bindings
templateDelimiters: ['{', '}'],
// Augment the event handler of the on-* binder
handler: function(target, event, binding) {
this.call(target, event, binding.view.models)
}
})
Modify the handler to
handler: function(target, event, binding) {
this.call(binding.model, event, binding.view.models)
}
So that the model used for binding will be the context of handler
Related
I have a datatable, when I select an element, I have an edit button that appears that need to have the correct edit url, here what I have done:
// this is triggered on select AND unselect (multiselect = false)
function onRowSelectionChanged(row, evt) {
if (row.isSelected) {
vm.feed = row.entity;
} else {
delete vm.feed;
}
vm.selected = row.isSelected;
}
// this is the watcher that updates my button
$scope.$watch(function () {
return vm.selected;
}, function (newValue, oldValue) {
if (newValue === oldValue) {
return;
}
if (newValue) {
vm.actions.push({state: 'app.feeds.edit({feedId: ' + vm.feed.id + '})', icon: 'pencil'});
} else {
vm.actions.splice(-1);
}
});
My question is about this:
vm.actions.push({state: 'app.feeds.edit({feedId: ' + vm.feed.id + '})', icon: 'pencil'});
Is this a clean way of interpolating the value, or can I do better ? as the Feed is bound to the vm, how comes I cannot do this:
vm.actions.push({state: 'app.feeds.edit({feedId: vm.feed.id})', icon: 'pencil'});
If I write this last piece of code, I always get feeds//edit
Because the handlebars {{ }} are used in the html or templates.
if you want to add a value in a string with js you need to escape it with and concatenate it.
This is just how you can inject values into strings
I'm using selection Xtype and checkbox type property (CQ.form.Selection) to create a checkbox group in CQ5 (API docs at http://docs.adobe.com/docs/en/cq/5-6/widgets-api/index.html?class=CQ.form.Selection).
But I want to override setValue, getValue and validate functions of it in order to meet my requirement. How can I do that via JCR node declaration ?
many thanks and appreciate.
Not sure what you mean by "do that via JCR node declaration". But if you need to make a few additional steps with standard xtype you just need to create a custom xtype, which wraps the standard one, and use it.
Here is an example of JS-code that creates and registers new xtype (combines values of 2 different fields into a single value).
Ejst.CustomWidget = CQ.Ext.extend(CQ.form.CompositeField, {
/**
* #private
* #type CQ.Ext.form.TextField
*/
hiddenField: null,
/**
* #private
* #type CQ.Ext.form.ComboBox
*/
allowField: null,
/**
* #private
* #type CQ.Ext.form.TextField
*/
otherField: null,
constructor: function(config) {
config = config || { };
var defaults = {
"border": false,
"layout": "table",
"columns":2
};
config = CQ.Util.applyDefaults(config, defaults);
Ejst.CustomWidget.superclass.constructor.call(this, config);
},
// overriding CQ.Ext.Component#initComponent
initComponent: function() {
Ejst.CustomWidget.superclass.initComponent.call(this);
this.hiddenField = new CQ.Ext.form.Hidden({
name: this.name
});
this.add(this.hiddenField);
this.allowField = new CQ.form.Selection({
type:"select",
cls:"ejst-customwidget-1",
listeners: {
selectionchanged: {
scope:this,
fn: this.updateHidden
}
},
optionsProvider: this.optionsProvider
});
this.add(this.allowField);
this.otherField = new CQ.Ext.form.TextField({
cls:"ejst-customwidget-2",
listeners: {
change: {
scope:this,
fn:this.updateHidden
}
}
});
this.add(this.otherField);
},
// overriding CQ.form.CompositeField#processPath
processPath: function(path) {
console.log("CustomWidget#processPath", path);
this.allowField.processPath(path);
},
// overriding CQ.form.CompositeField#processRecord
processRecord: function(record, path) {
console.log("CustomWidget#processRecord", path, record);
this.allowField.processRecord(record, path);
},
// overriding CQ.form.CompositeField#setValue
setValue: function(value) {
var parts = value.split("/");
this.allowField.setValue(parts[0]);
this.otherField.setValue(parts[1]);
this.hiddenField.setValue(value);
},
// overriding CQ.form.CompositeField#getValue
getValue: function() {
return this.getRawValue();
},
// overriding CQ.form.CompositeField#getRawValue
getRawValue: function() {
if (!this.allowField) {
return null;
}
return this.allowField.getValue() + "/" +
this.otherField.getValue();
},
// private
updateHidden: function() {
this.hiddenField.setValue(this.getValue());
}
});
// register xtype
CQ.Ext.reg('ejstcustom', Ejst.CustomWidget);
Creating custom xtype in AEM6's touch UI
I have a CompositeView that I am working with in Marionette that when the collection fetches can potentially pull 1000s of records back from the API. The problem I am seeing is that when the UI loads, the browser wigs out and the script freezes the browser up.
I have tried following this blog post (http://lostechies.com/derickbailey/2011/10/11/backbone-js-getting-the-model-for-a-clicked-element/), but can't seem to figure out how to translate that to working in Marionette.
Here is my CompositeView:
var EmailsMenuView = Marionette.CompositeView.extend({
template: _.template(templateHTML),
childView: EmailItemView,
childViewOptions: function(model, index){
return {
parentIndex: index,
parentContainer: this.$el.closest('form')
};
},
childViewContainer: '.emails',
ui: {
emails: '.emails',
options: '.email-options',
subject: "#subject_line",
fromName: "#from_name",
fromEmail: "#from_email",
thumbnail: '.thumbnail img',
emailName: '.thumbnail figcaption'
},
events: {
'change #ui.subject': 'onSubjectChange',
'change #ui.fromName': 'onFromNameChange',
'change #ui.fromEmail': 'onFromEmailChange'
},
childEvents: {
'menu:selected:email': 'onMenuEmailSelect'
},
collectionEvents: {
'change:checked': 'onEmailSelected'
},
behaviors: {
EventerListener: {
behaviorClass: EventerListener,
eventerEvents: {
'menu:next-click': 'onNext',
'menu:previous-click': 'onPrev'
}
},
LoadingIndicator: {
behaviorClass: LoadingIndicator,
parentClass: '.emails'
}
},
renderItem: function(model) {
console.log(model);
},
/**
* Runs automatically during a render
* #method EmailsMenuView.onRender
*/
onRender: function () {
var colCount, showGridList, selected,
threshold = 300;
if(this.model.get('id')) {
selected = this.model;
}
// refresh its list of emails from the server
this.collection.fetch({
selected: selected,
success: function (collection) {
colCount = collection.length;
console.log(colCount);
},
getThumbnail: colCount <= threshold
});
this.collection.each(this.renderItem);
var hasEmail = !!this.model.get('id');
this.ui.emails.toggle(!hasEmail);
this.ui.options.toggle(hasEmail);
eventer.trigger(hasEmail ? 'menu:last' : 'menu:first');
}
});
My ItemView looks like this:
var EmailItemView = Marionette.ItemView.extend({
tagName: 'article',
template: _.template(templateHTML),
ui: {
radio: 'label input',
img: 'figure img',
figure: 'figure'
},
events: {
'click': 'onSelect',
'click #ui.radio': 'onSelect'
},
modelEvents: {
'change': 'render'
},
behaviors: {
Filter: {
behaviorClass: Filter,
field: "email_name"
}
},
/**
* initializes this instance with passed options from the constructor
* #method EmailItemView.initialize
*/
initialize: function(options){
this.parentIndex = options.parentIndex;
this.parentContainer = options.parentContainer;
this.listenTo(eventer, 'menu:scroll', this.onScroll, this);
},
/**
* Runs automatically when a render happens. Sets classes on root element.
* #method EmailItemView.onRender
*/
onRender: function () {
var checked = this.model.get("checked");
this.$el.toggleClass('selected', checked);
this.ui.radio.prop('checked', checked);
},
/**
* Runs after the first render, only when a dom refrsh is required
* #method EmailItemView.onDomRefresh
*/
onDomRefresh: function(){
this.onScroll();
},
/**
* Marks this item as checked or unchecked
* #method EmailItemView.onSelect
*/
onSelect: function () {
this.model.collection.checkSingle(this.model.get('id'));
this.trigger('menu:selected:email');
},
templateHelpers: {
formattedDate: function() {
var date = this.updated_ts.replace('#D:', '');
if (date !== "[N/A]") {
return new Moment(date).format('dddd, MMMM DD, YYYY [at] hh:mm a');
}
}
},
/**
* Delays the load of this view's thumbnail image until it is close to
* being scrolled into view.
* #method EmailItemView.onScroll
*/
onScroll: function () {
if (this.parentIndex < 10) {
// if it's one of the first items, just load its thumbnail
this.ui.img.attr('src', this.model.get('thumbnail') + '?w=110&h=110');
} else if (this.parentContainer.length && !this.ui.img.attr('src')) {
var rect = this.el.getBoundingClientRect(),
containerRect = this.parentContainer[0].getBoundingClientRect();
// determine if element is (or is about to be) visible, then
// load thumbnail image
if (rect.top - 300 <= containerRect.bottom) {
this.ui.img.attr('src', this.model.get('thumbnail') + '?w=110&h=110');
}
}
}
});
I am trying to adjust my CompositeView to work where I can build the HTML and then render the cached HTML versus appending 1000s of elements
Render all 1000 elements isn't good idea - DOM will become too big. Also on fetch there are parse for all 1000 models, and it works synchroniously. So there are two ways - load less data, or splist data to parse/render by chunks
I'm using AngularJS to populate my datatable. What I want to know is how can I populate the datatable based on the dropdownlist
This is my dropdownlist
<div>
Get Users with Role:
<select id="ddlRole" data-ng-model="selectedRole" data-ng-change="populateDataTable()" data-ng-options="v.name for (k,v) in roles"></select>
<input type="hidden" value="{{selectedRole}}" />
</div>
This is my angular code
$scope.roles = [
{name: 'XXX' },
{name: 'YYY' }
];
$scope.selectedRole = $scope.roles[0];
//onchange event
$scope.populateDataTable = function () {
$scope.selectedRole = $scope.selectedRole.name;
RefreshDataTable(); //TODO
};
How can I change this to make an ajax call to retreive the data, populate the datatable based on the dropdownlist value and retain the value of dropdownlist as well.
I'm sure we can do this using JQuery but I dont want to mix these and make a mess. Is there any way I can acheive this using AngularJS?
Here is a simple data table directive:
appModule.directive('dataTable', [function () {
return function (scope, element, attrs) {
// apply DataTable options, use defaults if none specified by user
var options = {};
if (attrs.myTable.length > 0) {
options = scope.$eval(attrs.myTable);
} else {
options = {
"bStateSave": true,
"iCookieDuration": 2419200, /* 1 month */
"bJQueryUI": true,
"bPaginate": false,
"bLengthChange": false,
"bFilter": false,
"bInfo": false,
"bDestroy": true
};
}
// Tell the dataTables plugin what columns to use
// We can either derive them from the dom, or use setup from the controller
var explicitColumns = [];
element.find('th').each(function (index, elem) {
explicitColumns.push($(elem).text());
});
if (explicitColumns.length > 0) {
options["aoColumns"] = explicitColumns;
} else if (attrs.aoColumns) {
options["aoColumns"] = scope.$eval(attrs.aoColumns);
}
// aoColumnDefs is dataTables way of providing fine control over column config
if (attrs.aoColumnDefs) {
options["aoColumnDefs"] = scope.$eval(attrs.aoColumnDefs);
}
if (attrs.fnRowCallback) {
options["fnRowCallback"] = scope.$eval(attrs.fnRowCallback);
}
// apply the plugin
var dataTable = element.dataTable(options);
// watch for any changes to our data, rebuild the DataTable
scope.$watch(attrs.aaData, function (value) {
var val = value || null;
if (val) {
dataTable.fnClearTable();
dataTable.fnAddData(scope.$eval(attrs.aaData));
}
});
if (attrs.useParentScope) {
scope.$parent.dataTable = dataTable;
} else {
scope.dataTable = dataTable;
}
};
}]);
Then initialize it in your controller. Override fnServerData method, append your selected value (selected role) and filter data on server side.
$scope.overrideOptions = {
"bStateSave": true,
"iDisplayLength": 8,
"bProcessing": false,
"bServerSide": true,
"sAjaxSource": 'Data/Get',
"bFilter": false,
"bInfo": true,
"bLengthChange": false,
"sServerMethod": 'POST', ,
"fnServerData": function(sUrl, aoData, fnCallback, oSettings) {
var data = {
dataTableRequest: aoData,
selectedDropDownValue: $scope.selectedRole
};
$http.post(sUrl, data).success(function (json) {
if (json.sError) {
oSettings.oApi._fnLog(oSettings, 0, json.sError);
}
$(oSettings.oInstance).trigger('xhr', [oSettings, json]);
fnCallback(json);
});
}
};
var columnDefs = [
{
"mData": "id",
"bSortable": false,
"bSearchable": false,
"aTargets": ['tb-id']
},
{
"mData": "data",
"aTargets": ['tb-data']
}
];
Refresh the datatable on select change.
$scope.populateDataTable = function () {
if ($scope.dataTable) {
$scope.dataTable.fnDraw();
}
};
Html markup:
<table class="display m-t10px" data-table="overrideOptions" ao-column-defs="columnDefs">
<thead>
<tr>
<th class="tb-id"></th>
<th class="tb-data></th>
</tr>
</thead>
<tbody>
</tbody>
</table>
Hope above your code is in controller.
Inject $http and make a $http get or post call
$scope.populateDataTable = function () {
$scope.selectedRole = $scope.selectedRole.name;
$http.get('api/controller', function(result){
//response from the service call in result
});
};
Is there any way to access the parent component (via this.ownerCt) in the initComponent function?
While trying to access it via this.ownerCt, i found out that the ownerCt attribute is set after initComponent. So I do not know how i can hook in the initialization process of my component where i can change some parent's attributes.
I know this doesn't answer the question directly. I would have placed this in the comments to your question but I'm not allowed yet it would appear. If you are building breadcrumbs. I would look at extending the tab panel and creating a plugin for the Tab Bar that creates the kinda of navigation you want.
Ext.define('HOD.plugins.Breadcrumbs', {
// private
init : function(tabBar) {
tabBar.on('beforeadd', this.addIcons, this);
tabBar.on('beforeremove', this.handleTabRemove, this);
},
addIcons: function(tabBar, newTab, index, options) {
if (index > 0) {
newTab.iconCls = 'icon-arrow';
tabBar.items.each(function(tab) {
if (tab != newTab) {
tab.overCls = 'breadcrumbs-over'
}
});
}
},
handleTabRemove: function(tabBar, oldTab, options) {
var count = tabBar.items.getCount();
if (count > 1) {
var newTab = tabBar.items.getAt(count-2);
newTab.overCls = '';
newTab.removeCls('x-tab-breadcrumbs-over');
}
}
});
Then extend the tab panel so it uses the above plugin to style the tabs correctly.
Ext.define('HOD.view.GlobalNavigation', {
extend: 'Ext.tab.Panel',
border: false,
alias: 'widget.content',
requires: ['HOD.plugins.Breadcrumbs'],
tabBar: {
cls: 'breadcrumbs',
plugins: ['tabbarbreadcrumbs']
},
initComponent: function() {
this.on('tabchange', this.handleTabChange, this);
this.callParent(arguments);
},
push: function(tab) {
this.add(tab);
this.setActiveTab(tab);
},
pop: function() {
// Get the current cards;
var cards = this.getLayout().getLayoutItems();
if (cards.length > 1) {
this.setActiveTab(cards[cards.length-2]);
}
},
handleTabChange: function (tabPanel, newCard, oldCard, options) {
var cards = tabPanel.getLayout().getLayoutItems();
for (var i = (cards.length - 1); i > 0; i--) {
if (cards[i] !== newCard) {
this.remove(cards[i]);
} else {
break;
}
}
}
});
I've written up post about it here if you need more detail.
I would not recommend changing anything in the container from the inside element functions. Instead I would create an event in the element, fire that event and listen for it in the container.
This way your component will notify the container to do something, and container will do it itself.