I have a nested list of objects and I'm having trouble targeting individual ones. I have an array of Items, each of which has many Options. Right now an action is caught by my (intended) singular OptionController but changes are applied to all options.
So, how do I target just one Option?
Sample jsBin: http://jsbin.com/ONAsIpa/4/edit
Templates:
<script type="text/x-handlebars" data-template-name="application">
<p><strong>Ember.js example:</strong><br> Finally, a dashboard that monitors 'items', & their child 'options'!!</p>
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="items">
<h2>Items:</h2>
<dl>
{{#each}}
<dt>Item: {{title}}</dt>
<dd>Cost: {{cost}}</dd>
<dd class='options'>{{render 'options' options}}</dd>
{{/each}}
</dl>
</script>
<script type="text/x-handlebars" data-template-name="options">
<dl>
{{#each option in controller }}
{{render option}}
{{/each}}
</dl>
</script>
<script type="text/x-handlebars" data-template-name="option">
<dt>Option cost: {{option.cost}}</dt>
<dd {{bindAttr class=":opt-btn isSelected"}}{{action 'toggleOption'}}>Pick Me!</dd>
</script>
Ember Objects:
App = Ember.Application.create();
/**** ROUTES ****/
App.Router.map(function() {
this.resource('items');
});
App.IndexRoute = Ember.Route.extend({
redirect: function() {
this.transitionTo('items');
}
});
App.ItemsRoute = Ember.Route.extend({
model: function() {
var a = Em.A();
a.pushObject( App.Item.create({title: 'A', cost: '100', options: buildMockOptions('A')}));
a.pushObject( App.Item.create({title: 'B', cost: '200', options: buildMockOptions('B')}));
a.pushObject( App.Item.create({title: 'C', cost: '300', options: buildMockOptions('C')}));
return a;
}
});
buildMockOptions = function(someVar){
var arr = [];
for(var i = 0;i<3;i++){
var opt = App.Option.create({someOption: 'Option for ' + someVar + ': ' + i});
opt.cost = 5*i;
arr.pushObject(opt);
}
return arr;
};
/**** MODELS ****/
App.Item = Ember.Object.extend({
title: '',
cost: '',
quantity: '',
options: null,
totalOptionsCost: function(){
var j = this.get('options').reduce(
function(prevValue, currentValue, index, array){
return prevValue + parseInt(currentValue.get('cost'), 10); }, 0);
return j;
}.property('options.#each.cost')
});
App.Option = Ember.Object.extend({
someOption: '',
cost: ''
});
/**** CONTROLLERS ****/
App.ItemsController = Ember.ArrayController.extend({
totalOptions: function(){
var opts = this.mapBy('options');
return [].concat.apply([], opts).length;
}.property(),
totalOptionCost: function(){
var sum = this.reduce(function(prev, curr){
return prev + curr.get('totalOptionsCost');},0);
return sum;
}.property('#each.totalOptionsCost'),
actions: {
toggleOption: function(opt) {
alert('ItemsController caught action');
}
}
});
App.OptionController = Ember.Controller.extend({
actions: {
toggleOption: function(opt) {
this.set('isSelected', !this.get('isSelected') );
alert('OptionController caught action');
}
}
});
The option controller isn't backed by a model (since it's extending controller). It was almost as if this was a partial converted. So it's mostly a scoping issue.
App.OptionController = Ember.ObjectController.extend({
{{render 'option' option}}
{{cost}} instead of {{option.cost}}
http://jsbin.com/OjOvarE/1/edit
Related
I want to add a custom button to the Summernote toolbar that opens up a dialog that has a textbox for a URL and several checkboxes for settings. I then want to use the info from the dialog to scrape web pages and do processing on the content. The ultimate goal is to place the scraped content into the editor starting where the cursor is. I've searched and found some code on creating a custom button, but not any solid examples of implementing a dialog. I went through the summernote.js code to see how the Insert Image dialog works and that left me really confused. The test code I've got so far is in the code block, below. Thanks in advance to anyone who can help get me sorted out.
var showModalDialog = function(){
alert("Not Implemented");
};
var AddWiki = function(context) {
var ui = $.summernote.ui;
var button = ui.button({
contents: '<i class="fa fa-plus"/> Add Wiki',
tooltip: "Set a New Wiki",
class: "btn-primary",
click: function() {
showModalDialog();
}
});
return button.render();
};
$(".tw-summernote-instance textarea").summernote({
airMode: false,
dialogsInBody: false,
toolbar: [["mybutton", ["customButton"]]],
buttons: {
customButton: AddWiki
},
callbacks: {
onInit: function(e) {
var o = e.toolbar[0];
jQuery(o)
.find("button:first")
.addClass("btn-primary");
}
}
});
I found a good, simple example of what I wanted to do. Here's the code:
(function(factory) {
/* global define */
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(['jquery'], factory);
} else if (typeof module === 'object' && module.exports) {
// Node/CommonJS
module.exports = factory(require('jquery'));
} else {
// Browser globals
factory(window.jQuery);
}
}(function($) {
$.extend($.summernote.plugins, {
'synonym': function(context) {
var self = this;
var ui = $.summernote.ui;
var $editor = context.layoutInfo.editor;
var options = context.options;
context.memo('button.synonym', function() {
return ui.button({
contents: '<i class="fa fa-snowflake-o">',
tooltip: 'Create Synonym',
click: context.createInvokeHandler('synonym.showDialog')
}).render();
});
self.initialize = function() {
var $container = options.dialogsInBody ? $(document.body) : $editor;
var body = '<div class="form-group">' +
'<label>Add Synonyms (comma - , - seperated</label>' +
'<input id="input-synonym" class="form-control" type="text" placeholder="Insert your synonym" />'
'</div>'
var footer = '<button href="#" class="btn btn-primary ext-synonym-btn">OK</button>';
self.$dialog = ui.dialog({
title: 'Create Synonym',
fade: options.dialogsFade,
body: body,
footer: footer
}).render().appendTo($container);
};
// You should remove elements on `initialize`.
self.destroy = function() {
self.$dialog.remove();
self.$dialog = null;
};
self.showDialog = function() {
self
.openDialog()
.then(function(data) {
ui.hideDialog(self.$dialog);
context.invoke('editor.restoreRange');
self.insertToEditor(data);
console.log("dialog returned: ", data)
})
.fail(function() {
context.invoke('editor.restoreRange');
});
};
self.openDialog = function() {
return $.Deferred(function(deferred) {
var $dialogBtn = self.$dialog.find('.ext-synonym-btn');
var $synonymInput = self.$dialog.find('#input-synonym')[0];
ui.onDialogShown(self.$dialog, function() {
context.triggerEvent('dialog.shown');
$dialogBtn
.click(function(event) {
event.preventDefault();
deferred.resolve({
synonym: $synonymInput.value
});
});
});
ui.onDialogHidden(self.$dialog, function() {
$dialogBtn.off('click');
if (deferred.state() === 'pending') {
deferred.reject();
}
});
ui.showDialog(self.$dialog);
});
};
this.insertToEditor = function(data) {
console.log("synonym: " + data.synonym)
var dataArr = data.synonym.split(',');
var restArr = dataArr.slice(1);
var $elem = $('<span>', {
'data-function': "addSynonym",
'data-options': '[' + restArr.join(',').trim() + ']',
'html': $('<span>', {
'text': dataArr[0],
'css': {
backgroundColor: 'yellow'
}
})
});
context.invoke('editor.insertNode', $elem[0]);
};
}
});
}));
I've created a nice little KendoUI grid with drag and drop. It works great... as long as I have the developer tools open. Any idea why this would work with the dev tools open, but doesn't work at all with just using the browser?
Edit to add the code:
my cshtml page:
<div id="DisplayOrderGridContainer">
<div class="validation-error-box" id="errorMessages" style="display:none">
<span>Error!</span>
<ul id="message">
<li>The Record you attempted to edit was modified by another user after you got the original value. The edit operation was canceled.</li>
</ul>
</div>
<div style="padding-bottom: 15px;">
#Html.ActionLink("Back", Model.BackActionName, Model.ControllerName, Model.BackRouteValues, new { id = Model.ControllerName + "_Back", #class = "k-button" })
#if (Model.AllowClearingDisplayOrder)
{
#Html.ActionLink("Clear Order", Model.ClearActionName, Model.ControllerName, Model.BackRouteValues, new { #id = "clear-button", #class = "k-button float-right" })
}
</div>
<div id="KendoGridContainer">
<div id="ChangeDisplayOrderGrid"
class="grid"
data-role="grid"
data-bind="source:data, events:{dataBound:onDataBound,columnHide:OnColumnHide,columnShow:OnColumnShow}"
data-filterable="false"
data-sortable="false"
data-column-menu="true"
data-row-template="rowTemplate"
#*data-toolbar='[{ "template": kendo.template($("#toolbarTemplate").html()) }]'*#
data-columns='[{title:"Short Name", field:"NameField", width: 80, headerAttributes:{id: "#Model.ControllerName" + "_ShortName"}},
{title:"Description", field:"DescriptionField", width:300, headerAttributes:{id: "#Model.ControllerName" + "_Description"}},
{title:"Display Order", field:"Display", width:140, headerAttributes:{id: "#Model.ControllerName" + "_Display"}},
{title:"Value", field:"Value", hidden: true, headerAttributes:{id: "#Model.ControllerName" + "_Value"}}]'>
</div>
<script id="rowTemplate" type="text/x-kendo-tmpl">
<tr class="k-master-row" data-uid="#:uid#">
<td class="text-right">#=NameField#</td>
<td class="text-right">#=DescriptionField#</td>
<td class="text-right">#=Display#</td>
<td class="text-right" style="display:none;">#=Value#</td>
</tr>
</script>
<div id="grid" data-grid-url="#(Url.Action(Model.ActionName, Model.ControllerName))" data-grid-viewdataid="#ViewData.ID()"></div>
</div>
<input type="hidden" id="displayOrderId" />
<input type="hidden" id="Id" />
my js page:
$(document).ready(function () {
UnsavedWarningsModule.ClearUnsavedChanges();
var newData = new kendo.data.DataSource({
transport: {
read: {
url: $('#grid').attr('data-grid-url'),
dataType: "json"
}
},
schema: {
model: {
id: "Id"
}
}
})
var viewModel = new kendo.observable({
data: newData,
onDataBound: function (e) {
pfsKendoGridEvents.SetSelectedRow_MVVMGrid("KendoGridContainer", e.sender, $('#grid').attr('data-grid-viewdataId'))
},
OnColumnHide: function (e) {
pfsKendoGridEvents.OnHideShowColumns(e);
},
OnColumnShow: function (e) {
pfsKendoGridEvents.OnHideShowColumns(e);
}
});
kendo.bind($("#DisplayOrderGridContainer"), viewModel);
kendo.addDragAndDropToGrid = function (gridId, rowClass, viewModel) {
if (!gridId) { throw "Parameter [gridId] is not set."; }
if (!rowClass) { throw "Parameter [rowClass] is not set."; }
$(rowClass).kendoDraggable({
hint: function (element) {
var shortName = element[0].cells[0].firstChild.data;
var desc = element[0].cells[1].firstChild.data;
var dispOrder = element[0].cells[2].firstChild.data;
element[0].innerHTML = "<td class=\"text-right dragOver\" style=\"width:95px\">" + shortName + "</td><td class=\"text-right dragOver\" style=\"width:382px\">" + desc + "</td><td class=\"text-right dragOver\" style=\"width:173px\">" + dispOrder + "</td>";
return element;
},
axis: "y",
container: $(gridId)
});
$(gridId).kendoDropTargetArea({
filter: rowClass,
drop: function (e) {
var srcUid = e.draggable.element.data("uid");
var tgtUid = e.dropTarget.data("uid");
var ds = $(gridId).data("kendoGrid").dataSource;
var srcItem = ds.getByUid(srcUid);
var tgtItem = ds.getByUid(tgtUid);
var dstIdx = ds.indexOf(tgtItem);
ds.remove(srcItem);
ds.insert(dstIdx, srcItem);
e.draggable.destroy();
UnsavedWarningsModule.SetUnsavedChanges();
kendo.addDragAndDropToGrid(gridId, rowClass, viewModel);
},
dragenter: function (e) {
e.draggable.hint.css("opacity", 0.3);
},
dragleave: function (e) {
e.draggable.hint.css("opacity", 1);
var srcUid = e.draggable.element.data("uid");
var tgtUid = e.dropTarget.data("uid");
var ds = $(gridId).data("kendoGrid").dataSource;
var srcItem = ds.getByUid(srcUid);
var srcDispOrd = srcItem.Display;
var tgtItem = ds.getByUid(tgtUid);
var tgtDispOrd = tgtItem.Display;
var dstIdx = ds.indexOf(tgtItem);
//--update display orders after dropping
ds._data.forEach(function (data) {
//if dragging it to a spot with higher dispOrder
if (tgtDispOrd > srcDispOrd) {
if (data.Display <= tgtDispOrd && data.Display > srcDispOrd) {
data.Display -= 1;
}
}
//if dragging it to a spot with lower dispOrder
if (srcDispOrd > tgtDispOrd) {
if (data.Display >= tgtDispOrd && data.Display < srcDispOrd) {
data.Display += 1;
}
}
});
srcItem.Display = tgtDispOrd;
//--end
ds.remove(srcItem);
ds.insert(dstIdx, srcItem);
}
});
};
var dataService = (function () {
"use strict";
var self = {};
self.getAddresses = function () {
var data = new kendo.data.ObservableArray([newData]);
// Manual create a promise, so this function mimicks an Ajax call.
var dfd = new $.Deferred();
dfd.resolve(data);
return dfd.promise();
};
return self;
})(kendo);
var controller = (function (dataService, viewModel) {
"use strict";
var _dataService = dataService;
var _vm = viewModel;
var self = {};
self.handleAddressesRefresh = function (data) {
_vm.set("addresses", new kendo.data.DataSource({ data: data }));
kendo.bind($("#KendoGridContainer"), _vm);
kendo.addDragAndDropToGrid("#ChangeDisplayOrderGrid", ".k-master-row", _vm);
};
self.show = function () {
$.when(_dataService.getAddresses())
.then(self.handleAddressesRefresh);
};
return self;
})(dataService, viewModel);
controller.show();});
I think it's something to do with the timing of the loading of the page, possibly with the promise I'm using?
Thanks!
I am unable to get region name from code of region. My region click event work fine and I also get code properly. My code is:
<div id="map" style="width: 600px; height: 400px"></div>
<script type="text/javascript">
$(function () {
new jvm.MultiMap({
container: $('#map'),
maxLevel: 1,
main: {
map: 'us_lcc_en'
},
mapUrlByCode: function (code, multiMap) {
return 'js/counties/jquery-jvectormap-data-' +
code.toLowerCase() + '-' +
multiMap.defaultProjection + '-en.js';
}
});
$('#map').bind('regionClick.jvectormap', function (event, code) {
var map = $('#map').vectorMap('get', 'mapObject');
alert(map.getRegionName(code));
});
});
</script>
HTML CODE
<div id="map2" style="width: 100%; height: 400px"></div>
Jquery Code
$('#map2').vectorMap({
map: 'world_mill_en',
series: {
regions: [{
scale: ['#C8EEFF', '#0071A4'],
normalizeFunction: 'polynomial',
values: gdpData
}]
},
onRegionClick: function (event, code) {
******* Code To Show Region Name *******
/* Here We are getting Map Object */
var map=$('#map2').vectorMap('get', 'mapObject');
/* Here Using Map Object we are using Inbuilt GetRegionName
function TO Get Regionname for a region Code */
var regionName = map.getRegionName(code);
console.log("Code: "+code+"<br/>");
console.log("Region Name: "+regionName+"<br/>");
******* Code To Show Region Name *******
}
});
I could not figure out how to get the region name from the onRegionClick for a multi map. This is my hack to get what is needed.
var drillDownUSMap;
var stateCode;
var countyName;
$(document).ready(function () {
drillDownUSMap = new jvm.MultiMap({
container: $('#map'),
maxLevel: 1,
main: {
map: 'us_lcc_en',
onRegionClick: function (e, code) {
var map = $('#map div').vectorMap('get', 'mapObject');
var regionName = map.getRegionName(code);
stateCode = code.toLowerCase();
console.log(regionName)
}
},
mapUrlByCode: function (code, multiMap) {
return 'js/counties/jquery-jvectormap-data-' +
code.toLowerCase() + '-' +
multiMap.defaultProjection + '-en.js';
},
mapNameByCode: function (code, multiMap) {
return code.toLowerCase() + '_' +
multiMap.defaultProjection + '_en';
}
});
$(document).on('click', '.jvectormap-region.jvectormap-element', function (evt) {
var regionElem = $(this);
var stateJSFileName = stateCode + "_" + drillDownUSMap.defaultProjection + "_en";
var regionCode = regionElem.attr('data-code');
if (drillDownUSMap.maps[stateJSFileName]) {
countyName = drillDownUSMap.maps[stateJSFileName].regions[regionCode].config.name;
if (regionElem.hasClass("selected-region")) {
regionElem.removeClass("selected-region");
} else {
regionElem.addClass("selected-region");
}
}
});
});
Add this code
onRegionClick:function(event, code) {
var name = (code);
alert(name);
}
All Script Like This
<div id="map" style="width: 600px; height: 400px"></div>
<script type="text/javascript">
$(function () {
new jvm.MultiMap({
container: $('#map'),
maxLevel: 1,
main: {
map: 'us_lcc_en'
},
mapUrlByCode: function (code, multiMap) {
return 'js/counties/jquery-jvectormap-data-' +
code.toLowerCase() + '-' +
multiMap.defaultProjection + '-en.js';
}
});
onRegionClick:function(event, code) {
var name = (code);
alert(name);
}
});
</script>
2I have an Ember app which connects to an api from where it gets articles. I make use of pagination to get 10 articles per request. This works. But now I wanted to add sorting to the request. I implemented this by using the extra parameter in the store.find.
However, for some reason if I use the 'return this.store.find('article', params);' instead of 'return this.store.find('article');' new articles (still requested and added correctly to the store!) in the getMore function are not beiing displayed or rendered. But when i remove the params parameter from store.find in model, it does work. What could be the case here?
templates/articles.hbs
<script type="text/x-handlebars" data-template-name="articles">
{{#each itemController="article"}}
<div class="item">
//...
</div>
{{/each}}
</script>
routes/articles.js
import Ember from 'ember';
export default Ember.Route.extend(Ember.UserApp.ProtectedRouteMixin, {
model: function(params) {
var params2 = {page: 1, per_page: 10, sort: params.sort};
return this.store.find('article', params2);
},
setupController: function(controller, model) {
controller.set('content', model);
},
actions:{
//...
},
getMore: function() {
// don't load new data if we already are
//if (this.get('loadingMore')) return;
//this.set('loadingMore', true);
var meta = this.store.metadataFor("article");
if (meta.hasmore) {
var controller = this.get('controller'),
nextPage = controller.get('page') + 1,
perPage = controller.get('perPage'),
sorting = controller.get('sort'),
items;
var params = {page: nextPage, per_page: perPage, sort: sorting};
this.store.findQuery('article', params).then(function (articles) {
controller.set('page', controller.get('page') + 1);
//this.set('loadingMore', false);
});
}
else{
$('#pagination_spinner').hide();
}
},
queryParamsDidChange: function() {
this.refresh();
}
}
});
controllers/articles.js
import Ember from 'ember';
var ArticlesController = Ember.ArrayController.extend({
itemController: 'article',
queryParams: ['sort'],
sort: 'rating',
page: 1,
perPage: 10
});
export default ArticlesController;
views/articles.js
import Ember from 'ember';
export default Ember.View.extend({
didInsertElement: function(){
//this.scheduleMasonry();
this.applyMasonry();
// we want to make sure 'this' inside `didScroll` refers
// to the IndexView, so we use jquery's `proxy` method to bind it
//this.applyMasonry();
$(window).on('scroll', $.proxy(this.didScroll, this));
},
willDestroyElement: function(){
this.destroyMasonry();
// have to use the same argument to `off` that we did to `on`
$(window).off('scroll', $.proxy(this.didScroll, this));
},
// this is called every time we scroll
didScroll: function(){
if (this.isScrolledToBottom()) {
$('#pagination_spinner').addClass('active');
this.get('controller').send('getMore');
}
},
scheduleMasonry: (function(){
Ember.run.scheduleOnce('afterRender', this, this.applyMasonry);
}).observes('controller.model.#each'), //TODO check
applyMasonry: function(){
$('#pagination_spinner').removeClass('active');
var $galleryContainer = $('#galleryContainer');
$galleryContainer.imagesLoaded(function() {
// check if masonry is initialized
var msnry = $galleryContainer.data('masonry');
if ( msnry ) {
msnry.reloadItems();
// disable transition
var transitionDuration = msnry.options.transitionDuration;
msnry.options.transitionDuration = 0;
msnry.layout();
// reset transition
msnry.options.transitionDuration = transitionDuration;
} else {
// init masonry
$galleryContainer.masonry({
itemSelector: '.item',
columnWidth: 0,
"isFitWidth": true
});
}
});
},
destroyMasonry: function(){
$('#galleryContainer').masonry('destroy');
},
// we check if we are at the bottom of the page
isScrolledToBottom: function(){
var distanceToViewportTop = (
$(document).height() - $(window).height());
var viewPortTop = $(document).scrollTop();
if (viewPortTop === 0) {
// if we are at the top of the page, don't do
// the infinite scroll thing
return false;
}
return (viewPortTop - distanceToViewportTop === 0);
}
});
nothing smart coming to my mind, but maybe it's that...
You've got the line:
if (meta.hasmore) {
in your getMore() function. Is this the case that you've got this meta field in one response and forgot in the other?
There is the following can.Model:
var CaseModel = can.Model.extend({
findAll: function(args){
var def = $.Deferred();
args.sobject.retrieve(args.criteria, function(err, records) {//records should be populated into view
if(err) def.reject(err);
else def.resolve(records);
});
return def;
}}, {});
How should I implement the can.Control with init method to populate can.view with deffered=CaseModel.findAll({sobject: new SObjectModel.Case()}) like here:
this.element.html(can.view('recipes', Deffered));
and how these records are looped in mustache template:
{{#each ???? }}
Thanks
Here is an example of how to do it with deferreds:
var CaseModel = can.Model.extend({
findAll: function(args){
var def = $.Deferred();
setTimeout(function () {
console.log('time');
def.resolve([{name: 'sam'}]);
}, 1000);
return def.promise();
}}, {});
var Control = can.Control({
init: function (ele, options) {
var self = this;
var pro = CaseModel.findAll({});
can.view('view', {records: pro})
.then(function(frag) {
self.element.html(frag);
});
}
});
var control = new Control('.app');
<script type="text/mustache" id="view">
VIEW
<ul>
{{#each records}}
<li>
{{name}}
</li>
{{/each}}
</ul>
</script>
And here is a demo
http://jsbin.com/kedih/1/edit
You should be able to adapt this to include your args.sobject.retrieve instead of the setTimeout in the example.