I have a problem accessing model properties in Ember.js
Model:
App.Page = DS.Model.extend({
title: DS.attr('string'),
parent: DS.attr('number'),
});
Controller:
App.BlocksController = Ember.ArrayController.extend({
pages: function() {
return App.Page.find();
}.property(),
pageTree: function() {
var pages = this.get('pages.content'),
pageTree = new Tree();
for(var i = 0; i < pages.length; i++) {
console.log( pages[i], pages[i].id, pages[i].parent );
}
pageTree.insertList(pages);
return pageTree;
}.property('pages.#each'),
});
I use the pageTree property in a template/view. The console.log does print me objects that have been retrieved from the API (incl. id), however, I cannot access its properties (parent giving me undefined, but it's in the API response).
One console.log lines looks like this:
Object {id: "5", clientId: 19, type: function, data: Object, prematerialized: Object…}
"5"
undefined
I assume that using pages.#each as a property, the pageTree property should be bound to the (loaded) pages array.
Yes, pages is not the model that is controlled by the ArrayController, but a secondary set of models.
When I use {{#each pages}}{{parent}}{{/each}} in the template, it works! So I guess, the problem has to do with the data bindings?
For reference, I'm on Ember 1.0.0-rc.6.1, ember-data 0.13, ember-data-django-rest-adapter 0.13
Okay, thanks to the IRC:
pageTree: function() {
var pages = this.get('pages'),
pageTree = new Tree();
pages.forEach(function(page) {
//console.log(page, page.get('parent'));
pageTree.insertAt(page.get('parent'), page );
});
return pageTree;
}.property('pages.#each'),
So, no .content, but forEach and .get().
Related
I have a FullCalendar scheduler on a webapp which has 2 way databinding for resources and events, all working great. I want to be able to present the user with a dropdown that enables them to toggle the visibility of a column, ideally completely client side.
I have tried a combination of addResource / removeResource however my issue here is that a rerender of the calendar (e.g. when a new event is added) then displays the previously removed resource. I can work around this however would prefer a really simple approach using JS / CSS. I currently cannot find a way to set a resource to not be visible, or to have zero width - is this possible?
There is an easy way to do this:
Store resources in an array variable resourceData.
Create another array called visibleResourceIds to store the ids of any resources you want to show.
In the resources callback function, filter resourceData to only contain the resources where the resource id exists in visibleResourceIds. Return the filtered array and fullcalendar will only add the desired resources for you.
To remove a resource from view, simply remove the resource id from visibleResourceIds and refetchResources. To add the resource back in, add the id to visibleResourceIds and refetchResources. DONE.
JSFiddle
var resourceData = [
{id: "1", title: "R1"},
{id: "2", title: "R2"},
{id: "3", title: "R3"}
];
var visibleResourceIds = ["1", "2", "3"];
// Your button/dropdown will trigger this function. Feed it resourceId.
function toggleResource(resourceId) {
var index = visibleResourceIds.indexOf(resourceId);
if (index !== -1) {
visibleResourceIds.splice(index, 1);
} else {
visibleResourceIds.push(resourceId);
}
$('#calendar').fullCalendar('refetchResources');
}
$('#calendar').fullCalendar({
defaultView: 'agendaDay',
resources: function(callback) {
// Filter resources by whether their id is in visibleResourceIds.
var filteredResources = [];
filteredResources = resourceData.filter(function(x) {
return visibleResourceIds.indexOf(x.id) !== -1;
});
callback(filteredResources);
}
});
I had the same challenge. Instead of a dropdown, I use checkboxes, but the workings will be the same.
My resources are stored in a variable, when I uncheck a box, the resource is removed and the resource's object is added to another array with the resourceId as key, and the index added to the object to restore the object in the same column as it originally was. When re-checking the box, the object is added to the resources array and the resources refetched.
/* retrieve the resources from the server */
var planningResources;
var removedResource = [];
$.ajax({
url: '/planning/resources/',
method: 'get',
success: function (response) {
planningResources = response;
showCalendar();
}
, error: function () {
if (typeof console == "object") {
console.log(xhr.status + "," + xhr.responseText + "," + textStatus + "," + error);
}
}
});
/* create the calendar */
showCalendar = function () {
$('#calendar').fullCalendar({
...
});
}
/* checkbox on click */
$('.resource').click(function() {
var resourceId = $(this).val();
var hideResource = !$(this)[0].checked;
$('.status:checkbox:checked').each(function () {
});
if(hideResource) {
$.each(planningResources, function(index, value){
if( value && value.id == resourceId ) {
value.ndx = index;
removedResource[resourceId] = value;
planningResources.splice(index,1);
return false;
}
});
$('#planningoverview').fullCalendar(
'removeResource',
resourceId
);
}
else {
planningResources.splice(removedResource[resourceId].ndx, 0, removedResource[resourceId]);
$('#planningoverview').fullCalendar('refetchResources');
}
});
showCalendar();
It probably doesn't get first price in a beauty contest, but it works for me ...
Cheers
You can use the resourceColumns option for this. In the column objects you can set the width property to a number of pixels or a percentage. If you pass a function here you can easily handle the width property someplace else. Your hide/show function can then set the width to 0 to hide the column. After that you can trigger reinitView to update the view: $('#calendar').fullCalendar("reinitView");
I'm trying to create a simple Ractive adaptor to parse a value from the Color Thief (http://lokeshdhakar.com/projects/color-thief/) into a template with a defined mustache. (I know there may be better ways to achieve this, but there is a reason for why I'm using the adaptor route!)
I've set up a demo of what I have so far here - this the Ractive code part:
var colorThief = new ColorThief();
var img2 = document.getElementById('ctimage');
var imgColor;
Ractive.adapt.CTImg = {
filter: function ( object ) {
return object instanceof img2;
},
wrap: function ( ractive, img2, keypath, prefixer ) {
// Setup
return {
teardown: function(){
colorThief.destroy();
},
get: function(){
imgColor = colorThief.getColor(img);
},
set: function(property, value){
ractive.set('mainColor', imgColor);
},
reset: function(value){
}
}
}
};
var ractive = new Ractive({
target: '#container',
template: '#template',
adapt: [ 'CTImg' ],
data: {
mainColor: "rgb(97, 79, 112)" // this is what should be returned
}
});
My aim is to get the prominent color from the image given in the Codepen (above), pass it into Ractive (and to Color Thief by the adaptor), then output the resulting color on screen in the relevant mustache.
I can display a hard coded color OK in the template, so I know that the data keypath / reference is OK. However, my issue is getting the color back from Color Thief via the adaptor - the error I'm getting is Uncaught "TypeError: Cannot set property 'CTImg' of undefined".
I've checked through SO and the Ractive Github site to see if I can figure out what is going wrong, but my head is starting to spin!
Can anyone please help me to at least get the color to come back from Color Thief via the adaptor?
So adapt and adaptors are two different config objects. adaptors is a registry of adaptor definitions and adapt tells the component/instance what adaptors to use. There's no global adapt property.
For global registration of an adaptor, you need Ractive.adaptors.
Ractive.adaptors.CTImg = {...}
The next problem is actually how you use the adaptor. Adaptors require you to put the non-POJO data into the instance. The filter is run on the data and determines if the data needs to be adapted, and if so, does the setup. Then, it's the usual adaptor setup. get returns the value to Ractive, set sets the value to your custom object, etc.
Here's an updated example:
Ractive.adaptors.CTImg = {
filter: function ( object ) {
// Detect if the data is an image element
return object instanceof HTMLImageElement;
},
wrap: function ( ractive, object, keypath, prefixer ) {
// Set up color thief for this piece of data because it's an image
var colorThief = new ColorThief();
return {
teardown: function(){
colorThief.destroy();
},
get: function(){
// Return the replacement data
return colorThief.getColor(object);
},
set: function(property, value){
// We're not setting to color thief, leave empty
},
reset: function(value){
// Always replace the data when the data is changed
return false;
}
}
}
};
var ractive = new Ractive({
target: '#container',
template: '#template',
adapt: [ 'CTImg' ],
data: {
dominant: null
},
onrender: function(){
// set image on data. adaptor will capture it.
this.set('dominant', this.find('#ctimage'))
}
});
I probably misunderstood something but here is my problem on plunker.
I put the relevant code here anyway:
var app = angular.module('myApp', ['ngGrid']);
app.controller('MyCtrl', function($scope) {
var cellNameEditable =
'<cell-template model=COL_FIELD input=COL_FIELD entity=row.entity></cell-template>';
var cellNameDisplay =
'<div class="ngCellText" ng-class="col.colIndex()">{{row.getProperty(col.field)}}</div>';
$scope.myData= [{"id":1,"code":"1","name":"Ain"},{"id":2,"code":"2","name":"Aisne"},{"id":3,"code":"3","name":"Allier"},{"id":4,"code":"5","name":"Hautes-Alpes"},{"id":5,"code":"4","name":"Alpes-de-Haute-Provence"},{"id":6,"code":"6","name":"Alpes-Maritimes"},{"id":7,"code":"7","name":"Ardèche"},{"id":8,"code":"8","name":"Ardennes"},{"id":9,"code":"9","name":"Ariège"},{"id":10,"code":"10","name":"Aube"}];
$scope.gridOptions = {
data: 'myData',
multiSelect: false,
enableCellSelection: true,
enableRowSelection: false,
enableCellEditOnFocus: false,
rowHeight: 100,
columnDefs: [
{field:'id', displayName:'Id', visible: false},
{field:'code', displayName:'Code', enableCellEdit:true},
{
field:'name', displayName:'Name', enableCellEdit:true,
cellTemplate: cellNameDisplay,
editableCellTemplate: cellNameEditable
}
]
};
});
app.directive('cellTemplate', function () {
var cellTemplate =
'<div><form name="myForm" class="simple-form" novalidate>' +
'<input type="text" name="myField" ng-input="localInput" ng-model="localModel" entity="entity" required/>' +
'<span ng-show="myForm.myField.$error.required"> REQUIRED</span>' +
'localModel = {{localModel}} localInput = {{localInput}} entity = {{entity}}' +
'</form></div>';
return {
template: cellTemplate,
restrict: 'E',
scope: {
localModel:'=model',
localInput:'=input',
entity:'=entity'
},
controller: function ($scope) {
$scope.$on('ngGridEventStartCellEdit', function (event) {
console.log('cellTemplate controller - ngGridEventStartCellEdit fired');
$scope.oldEntity = angular.copy(event.currentScope.entity);
$scope.oldValue = angular.copy(event.currentScope.localModel);
});
$scope.$on('ngGridEventEndCellEdit', function(event) {
console.log('ngGridEventEndCellEdit fired');
if(event.currentScope.myForm.$valid) {
if(!angular.equals($scope.oldEntity, event.currentScope.entity)) {
alert('data saved !');
}
} else {
$scope.localModel = angular.copy($scope.oldValue);
$scope.localInput = angular.copy($scope.oldValue);
$scope.entity = angular.copy($scope.oldEntity);
}
});
}
};
});
Then explanations:
I have a ng-grid and based on the official example named "Excel-like Editing
Example" but with enableCellEditOnFocus option turned to false.
The cell "name" is defined in a directive containing a form to handle
data validation before updating the model.
I want to implement this behavior: When a user put invalid data, the
directive display error message and when the user leave the field, the
directive rollback data. If everything ok then I let the data updated.
The rollback part does not work. On the given plunker line 67 to 72 (last block on the code given here) it
fails to retore data. But my binding is with "=" so it should. Or maybe
because I am on the ngGridEventEndCellEdit event it breaks the links ?
I really don't understand why it fail.
So to reproduce my issue: enter in modification on a name cell, delete
all the data, REQUIRED is shown, then go out from the cell -> model is
not rolled back.
If you use a custom template, you should emit ngGridEventEndCellEdit event.
Is there a way to load a single entity of a Backbone collection (from the server)?
Backbone.Collection.extend({
url: '/rest/product'
});
The following code can load the entire collection with a collection.fetch() but how to load a single model? Backbone's documentation says clearly that GET can be done /collection[/id] but not how.
The model has to be declared that way:
Backbone.Model.extend({
url: function() {
return '/rest/product/'+this.id;
}
});
Using it is simple as:
var model = new ProductModel();
model.id = productId;
model.fetch({ success: function(data) { alert(JSON.stringify(data))}});
While we set
url:"api/user"
for the collection, the equivalent for the model is
urlRoot:"api/user"
this will make backbone automatically append the /id when you fetch()
collection.fetch( {data: { id:56 } } )
I did this:
Catalog.Categories.Collection = Backbone.Collection.extend({
fetchOne : function (id, success) {
var result = this.get(id);
if (typeof result !== 'undefined') {
console.log(result, 'result')
success.apply(result);
return;
}
var where = {};
where[this.model.prototype.idAttribute] = id;
var model = new this.model(where);
this.add(model);
console.log(this._idAttr, where, this.model)
model.fetch({success: function () {
success.apply(model);
}});
}
};
Now call it:
collection.fetchOne(id, function () {console.log(this)});
No more guessing if the model is already in the collection!. However, you have to use a call back as you can't depend on an intimidate result. You could use async false to get around this limitation.
just .fetch a Model.
So create a model with it's own .url function.
Something like
function url() {
return "/test/product/" + this.id;
}
I did this:
var Product = Backbone.Model.extend({});
var Products = Backbone.Collection.extend({
model: Product,
url: '/rest/product'
});
var products = new Products();
var first = new Product({id:1});
first.collection = products;
first.fetch();
This has the advantage of working when you're not using a REST storage engine (instead, using something like the HTML5 Local storage, or so forth)
I've been pouring over this for hours and I've yet to make much headway so I was hoping one of the wonderful denizens of SO could help me out. Here's the problem...
I'm implementing a tree via the jstree plugin for jQuery. I'm pulling the data with which I populate the tree programatically from our webapp via json dumped into an asp:HiddenField, basically like this:
JavaScriptSerializer serializer = new JavaScriptSerializer();
string json = serializer.Serialize(Items);
json = json.ToLower();
data.Value = json;
Then, the tree pulls the json from the hidden field to build itself. This works perfectly fine up until I try to persist data for which nodes are selected/opened. To simplify my problem I've hardcoded some json data into the tree and attempted to use the cookie plugin to persist the tree state data. This does not work for whatever reason. I've seen other issues where people need to load the plugins in a specific order, etc, this did not solve my issue. I tried the same setup with html_data and it works perfectly. With this working persistence I converted the cookie plugin to persist the data in a different asp:hiddenfield (we can't use cookies for this type of thing in our application.)
essentially the cookie operations are identical, it just saves the array of nodes as the value of a hidden field. This works with the html_data, still not with the json and I have yet to be able to put my finger on where it's failing.
This is the jQuery.cookie.js replacement:
jQuery.persist = function(name, value) {
if (typeof value != 'undefined') { // name and value given, set persist
if (value === null) {
value = '';
}
jQuery('#' + name).attr('value', value);
} else { // only name given, get value
var persistValue = null;
persistValue = jQuery('#' + name).attr('value');
return persistValue;
}
};
The jstree.cookie.js code is identical save for a few variable name changes.
And this is my tree:
$(function() {
$("#demo1").jstree({
"json_data": {
"data" : [
{
"data" : "A node",
"children" : [ "Child 1", "Child 2" ]
},
{
"attr": { "id": "li.node.id" },
"data" : {
"title": "li.node.id",
"attr": { "href": "#" }
},
"children": ["Child 1", "Child 2"]
}
]
},
"persistence": {
"save_opened": "<%= open.ClientID %>",
"save_selected": "<%= select.ClientID %>",
"auto_save": true
},
"plugins": ["themes", "ui", "persistence", "json_data"]
});
});
The data -is- being stored appropriately in the hiddenfields, the problem occurs on a postback, it does not reopen the nodes. Any help would be greatly appreciated.
After looking through this some more, I just wanted to explain that it appears to me that the issue is that the tree has not yet been built from the JSON_data when the persistence operations are being attempted. Is there any way to postpone these actions until after the tree is fully loaded?
If anyone is still attempting to perform the same type of operation on a jsTree version 3.0+ there is an easier way to accomplish the same type of functionality, without editing any of the jsTree's core JavaScript, and without relying on the "state" plugin (Version 1.0 - "Persistence"):
var jsTreeControl = $("#jsTreeControl");
//Can be a "asp:HiddenField"
var stateJSONControl = $("#stateJSONControl");
var url = "exampleURL";
jsTreeControl.jstree({
'core': {
"data": function (node, cb) {
var thisVar = this;
//On the initial load, if the "state" already exists in the hidden value
//then simply use that rather than make a AJAX call
if (stateJSONControl.val() !== "" && node.id === "#") {
cb.call(thisVar, { d: JSON.parse(stateJSONControl.val()) });
}
else {
$.ajax({
type: "POST",
url: url,
async: true,
success: function (json) {
cb.call(thisVar, json);
},
contentType: "application/json; charset=utf-8",
dataType: "json"
}).responseText;
}
}
}
});
//If the user changes the jsTree, save the full JSON of the jsTree into the hidden value,
//this will then be restored on postback by the "data" function in the jsTree decleration
jsTreeControl.on("changed.jstree", function (e, data) {
if (typeof (data.node) != 'undefined') {
stateJSONControl.val(JSON.stringify(jsTreeControl.jstree(true).get_json()));
}
});
This code will create a jsTree and save it's "state" into a hidden value, then upon postback when the jsTree is recreated, it will use its old "state" restored from the "HiddenField" rather than make a new AJAX call and lose the expansions/selections that the user has made.
Got it working properly with JSON data. I had to edit the "reopen" and "reselect" functions inside jstree itself.
Here's the new functioning reopen function for anyone who needs it.
reopen: function(is_callback) {
var _this = this,
done = true,
current = [],
remaining = [];
if (!is_callback) { this.data.core.reopen = false; this.data.core.refreshing = true; }
if (this.data.core.to_open.length) {
$.each(this.data.core.to_open, function(i, val) {
val = val.replace(/^#/, "")
if (val == "#") { return true; }
if ($(("li[id=" + val + "]")).length && $(("li[id=" + val + "]")).is(".jstree-closed")) { current.push($(("li[id=" + val + "]"))); }
else { remaining.push(val); }
});
if (current.length) {
this.data.core.to_open = remaining;
$.each(current, function(i, val) {
_this.open_node(val, function() { _this.reopen(true); }, true);
});
done = false;
}
}
if (done) {
// TODO: find a more elegant approach to syncronizing returning requests
if (this.data.core.reopen) { clearTimeout(this.data.core.reopen); }
this.data.core.reopen = setTimeout(function() { _this.__callback({}, _this); }, 50);
this.data.core.refreshing = false;
}
},
The problem was that it was trying to find the element by a custom attribute. It was just pushing these strings into the array to search when it was expecting node objects. Using this line
if ($(("li[id=" + val + "]")).length && $(("li[id=" + val + "]")).is(".jstree-closed")) { current.push($(("li[id=" + val + "]"))); }
instead of
if ($(val).length && $(val).is(".jstree-closed")) { current.push(val); }
was all it took. Using a similar process I was able to persist the selected nodes this way as well.
Hope this is of help to someone.