Let me use Posts and Comments to describe my problem. Inside my post_controller I wish to create a new record for a comment for that current post. What is the ember way to do this?
The relation is set like this:
App.Post = DS.Model.extend({
comments: hasMany('comment'),
});
App.Comment = DS.Model.extend({
post: belongsTo('post')
});
Inside my post_controller I want to create a record. I have this inside an action that is triggered from a template:
App.PostController = Ember.ObjectController.extend({
...
actions: {
createComment: function() {
var post = this.get('model'); // Edit: Forgot that I had this declared outside createRecord
var comment = this.store.createRecord('comment', {
content : "content",
post : post // This is where the problem is
});
}
}
});
However, I get an error saying: Uncaught TypeError: Cannot read property 'post' of undefined
How do I declare this relationship? Thanks.
Edit:
The ember-data error comes from this internal function in ember-data.js:
return Ember.computed(function(key, value) {
var data = get(this, 'data'),
store = get(this, 'store'), belongsTo, typeClass;
if (typeof type === 'string') {
typeClass = store.modelFor(type);
} else {
typeClass = type;
}
if (arguments.length === 2) {
Ember.assert("You can only add a '" + type + "' record to this relationship", !value || value instanceof typeClass);
return value === undefined ? null : value;
}
belongsTo = data[key]; // ERROR OCCURS HERE!
if (isNone(belongsTo)) { return null; }
store.fetchRecord(belongsTo);
return belongsTo;
}).property('data').meta(meta);
};
EDIT: Problem solved!
The problem was that I hade given comment an attribute called data. That attribute was in conflict with internal ember. Removing it made my code above work fine.
If you declare your post outside of the createRecord (ie- above the var comment = line)
var postModel = this.get('model');
You might be having an issue w/ the "this" keyword inside the createRecord scope
Update
also, do you get the same result for this.get('model') and this.get('content') (from outside that scope) ?
Related
Well fist time here, so sorry for the mess or the long text.
I pass $scope.alertsensorconfigs as tableData to my service, and wen I delete a item, my $scope.alertsensorconfigs changes but wen It try to reload the table I see that tableData had not changed.
Detailed explanation:
I have a site that uses NgTable to create a lot of tables in different pages, but all the logic to convert and filter the data that I want to show in the page was coded in the pages and it repeated a lot of times but it was working, so i decided to thy make a service that do all that.
So my problem is, I created the service, it i almost all working like before, but I notices that wen I delete a item from the table, it deletes the data but keep showing it on the table until I reload the page.
I notice that it is because even that my object on the page have changed after delete, wen it is inside my service the object apear to be the unchanged one, since I passed my object as parameter to my service i took it wold pass like a reference to the object and wen it changes on the page it wold apear changed in my service, but looks like it is not the case, it looks like wen it calls for the fist time my service it makes a copy of my object and wen it calls again it wont get the updated one.
My page code.
$scope.funcFilter = function(pfilter){
return function (item) {
return item.name.toUpperCase().indexOf(pfilter) >= 0
||
item.criticality1.toString().toUpperCase().indexOf(pfilter) >= 0
||
item.criticality2.toString().toUpperCase().indexOf(pfilter) >= 0
|| $filter('translate')
(item.active.toString().toUpperCase()).indexOf(pfilter) >= 0;
}
}
$scope.searchTable = {filter: ""};
$scope.tableParams =
NgTableDataService.getGenericTableParams($scope.alertsensorconfigs,
$scope.funcFilter, $scope.searchTable.filter)
Delete function in my page:
AlertSensorConfig.remove({id: obj.id}, function () {
$scope.alertsensorconfigs.splice($scope.alertsensorconfigs.indexOf(obj), 1);
$scope.tableParams.reload().then(function(data) {
if (data.length === 0 && $scope.tableParams.total() > 0) {
$scope.tableParams.page($scope.tableParams.page() - 1);
$scope.tableParams.reload();
}
});
},
My service:
angular.module('control-room').service('NgTableDataService',
function ($filter, NgTableParams, Session) {
var session = Session.get();
this.getGenericTableParams = function(tableData, funcFilter, searchTableFilter){
return new NgTableParams({
count: session.user.tablePagination,
filter: searchTableFilter
}, {
counts: rowsPerPageTemplate,
getData: function (params) {
if (params.filter() == ''){
var pfilter = '';
}else{
var pfilter = params.filter().filter.toUpperCase();
}
let filteredData = params.filter() ? $filter('filter')(tableData, funcFilter(pfilter)) : tableData;
if (!!filteredData && filteredData.length >= 0) {
params.total(filteredData.length);
var rowsPerPageTemplateWithAllData = rowsPerPageTemplate.slice();
var isFound = rowsPerPageTemplateWithAllData.some(function (element) {
return element === filteredData.length;
});
params.settings().counts = rowsPerPageTemplateWithAllData.filter(item=>item<filteredData.length)
if (filteredData.length >= 5){
params.settings().counts.push(filteredData.length);
}
rowsPerPageTemplateWithAllData.push(filteredData.length + (isFound ? 1 : 0));
if (session.user.tablePagination >= params.settings().counts[params.settings().counts.length-1]){
params.settings().count = params.settings().counts[params.settings().counts.length-1];
}else{
params.settings().count = session.user.tablePagination;
}
if (params.total() <= params.count()) {
params.page(1);
}
var x = $filter('orderBy')(filteredData, params.orderBy());
var y = x.slice((params.page() - 1) * params.count(), params.page() * params.count());
return y;
} else {
params.settings().counts = [];
return null;
}
}
});
};
And the ng-table function that reload the table after delete:
this.reload = function() {
var self = this,
pData = null;
settings.$loading = true;
prevParamsMemento = angular.copy(createComparableParams());
isCommittedDataset = true;
if (self.hasGroup()) {
pData = runInterceptorPipeline($q.when(settings.getGroups(self)));
} else {
pData = runInterceptorPipeline($q.when(settings.getData(self)));
}
log('ngTable: reload data');
var oldData = self.data;
return pData.then(function(data) {
settings.$loading = false;
errParamsMemento = null;
self.data = data;
event even when data === oldData
ngTableEventsChannel.publishAfterReloadData(self, data, oldData);
self.reloadPages();
return data;
}).catch(function(reason){
errParamsMemento = prevParamsMemento;
// "rethrow"
return $q.reject(reason);
});
};
there is some way to make sure the object I pas as parameter to my service is updated every time I call it, like some binding?
I think i manage to solve it.
The NgTable have his own reload(), but it wasn't working because what it does is, get the current filteredData and replace with the one tableData, but the tableData on my service was only set wen it was called the fist time and wasn't getting updated wen I updated the $scope.alertsensorconfigs.
So what I manage to do after a lot of headache, was to create in my service:
A var serviceTableData that receives the $scope.alertsensorconfigs wen the service is called so it is global in my service.
And my own reload() function that I replaced in every place that my controller used the NgTable reload().
My service reload() wen called receives as parameter from the controller the $scope.alertsensorconfigs after the controller delete or edit the item, then it will set the serviceTableData to the updated data received as param and then calls the NgTable reload().
So it ended like this:
angular.module().service('NgTableDataService',
function ($filter, NgTableParams, Session, MY_CONSTANTS) {
var session = Session.get();
var serviceTableParam = null;
var serviceTableData = null;
this.reloadTableData = function (tableDataAtt){
serviceTableData = tableDataAtt;
serviceTableParam.reload().then(function(data) {
if (data.length === 0 && serviceTableData.total() > 0) {
serviceTableParam.page(serviceTableParam.page() - 1);
serviceTableParam.reload();
}
return this.tableData = tableDataAtt;
});
};
this.getGenericTableParams = function(tableData, searchTableFilter, funcFilter){
serviceTableData = tableData;
return serviceTableParam = new NgTableParams({
// etc...
Edit : I misunderstood the question
According to your comment what you want is to update a variable in your service and retrieve it later.
What you can do is :
In your service
angular
.module('app.core')
.factory('', dataservice);
dataservice.$inject = ['$http'];
function dataservice($http) {
return {
myFunction: yourServiceFunction,
myData: myData
};
var myData = [];
function yourServiceFunction(tableData) {
// DO SOMETHING
myData = tableData;
// DO SOMETHING
}
}
Like this you can access to myData in your service or in your controller which is updated in your function with the value of tableData that you passed when you called your function.
And if you want to update myData in your service everytime $scope.alertsensorconfigs changes, add a $watch to this variable and create a function in your service who is called whenever your $scope value change and which simply update myData in your service.
I hope that I answered to the right question this time :)
.
.
.
Here after : old response which do not answer to the question
there is some way to make sure the object I pas as parameter to my service is updated every time I call it, like some binding?
Yes.
I am sorry to not answer with your code but it is a bit hard to get into it.
The simpliest way is to do something like this :
$scope.alertsensorconfigs =
yourService.yourMethod($scope.alertsensorconfigs, param2, param3).tableData;
While your services' method does something like :
yourMethod = function (tableData, param2, param3) {
// DO SOMETHING
return { tableData : tableDataAfterModification, elem2 : 'elem2', elem3 : 'elem3' };
};
I am new in Vuejs and I get an unexpected output in my app. What my app does is to search on YouTube API for channels, and then adding those channels in a list.
Then I like to render the list of the subscribed channels, but always I get one item less. Alway the last inserted item is missing from the rendered list, while the item exists in my data.
This is the rendered output:
If you see the right column, under the text box, has only one item rendered, while in my Vue console I have two items under the channels_info key:
Then if I try to append yet another one item in the list, the console will display 3 items while the HTML render will display 2, and so on.
My code is the following:
var setup = function () {
app = new Vue(
{
el : '#sml_app',
data : {
channel_name : '',
errors : [],
channels_found : {},
no_channels_found : true,
next_page_token : '',
prev_page_token : '',
page_token : '',
subscriptions : [],
channels_info : {},
subscriptions_counter: 1
},
methods: {
fetch_channel_info : function ($channel_id) {
var self = this;
var base_api_url = 'https://www.googleapis.com/youtube/v3/channels';
var query_params = {
'part' : 'snippet,contentDetails',
'key' : 'My-ApiKey',
'maxResults': 1,
'id' : $channel_id
};
var get_params = '';
for (var key in query_params) {
if (get_params != '') {
get_params += '&';
}
get_params += key + '=' + encodeURIComponent(query_params[key]);
}
get_params = '?' + get_params;
axios.get(base_api_url + get_params).then(
function (data) {
data = 'data' in data ? data.data : {};
if (
typeof undefined !== typeof data.items &&
typeof undefined !== typeof data.items[0] &&
typeof undefined === typeof self.channels_info[$channel_id]
) {
var snippet = data.items[0].snippet;
var $key = self.subscriptions_counter + '-' + $channel_id;
self.channels_info[$key] = snippet;
self.subscriptions_counter += 1;
}
}
).catch(
function () {
self.errors.push(
'No channel found matching this channel id.');
}
);
},
// ...
append_to_subscriptions: function ($channel_id) {
if (-1 === this.subscriptions.indexOf($channel_id)) {
this.subscriptions.push($channel_id);
this.fetch_channel_info($channel_id);
// Todo-merianos: Create an AJAX request to set the options in
// database
}
}
}
}
);
};
While my HTML side is like that:
<div class="subscription" v-for="subscription in channels_info">
<span v-text="subscription.title"></span>
</div>
Do you see anything in wrong ? I don't understand why I have that strange output :/
Any sugestion please?
You're appending a new property to an object. I recommend reading this relevant section of the Vue.js documentation regarding object change detection caveats. Specifically, you can use Vue.set(object, key, value) to ensure that your new object key is detected and becomes reactive.
So, instead of self.channels_info[$key] = snippet; you might instead do something like Vue.set(this.channels_info, $key, snippet);.
Definitely read through some more of the documentation. I'm certain that you'll find a lot of value in the rest of the information on this topic.
I have a scope that is not updated after assignment inside a factory http call.
var form = {}
var requestForm = {}
requestForm['name'] = $scope.brand.name;
requestForm['country'] = $scope.brand.countryCode;
So I'm setting a $scope.mergeId as my initializer as value 0, then the tableFactory.setMergeRequest will call a http call in the factory file and will return an object, with a boolean and an id #.
$scope.mergeId = 0;
//set merge request/id
tableFactory.setMergeRequest(requestForm).then(function(data){
if(data.mergeRequestStatus){
console.log(data);
$scope.mergeId = data.insertedRequestId; //456
}else{
console.log('no merge id');
}
});
form['mergeId'] = $scope.mergeId;
The boolean will be true, and it should assign the id (456) to the $scope.mergeId. Then that scope will be used to assign the form['mergeId'] variable that will be used in another http call.
When I check the console, the form variable is at zero so it's not updated. I took out the initializer but then it's saying is undefined. The http call is sending back data, it's just the scope is not being updated.
Has anyone gone through this issue before? Should I change the setup for this http call? I tried to this, but it's not setting the right value, it's setting an object.
form['mergeId'] = tableFactory.setMergeRequest(requestForm).then(function(data){
if(data.mergeRequestStatus){
return data.insertedRequestId
}else{
return null
}
});
the response I got from this way, is a d, how do i get the value?
Please help, I've been stuck with this issue for a while. Your help will be appreciated.
ADDITIONAL INFO
This is the http call where tableFactory.setMergeRequest is triggered.
var setMergeRequest = function(object){
var mergeRequestCall = {
method: 'POST',
url: CONFIG.PYTHON_API_END_POINT + '/api/mergerequest',
data: object
}
var d = $q.defer();
$http(mergeRequestCall)
.success(function(response){
d.resolve(response);
}).error(function(response){
d.resolve([]);
});
return d.promise;
}
The response is :
{
"insertedRequestId": 456,
"mergeRequestStatus": true
}
You should set the value where the promise is resolved, not set the value to the function
tableFactory.setMergeRequest(requestForm).then(function(data){
if(data.mergeRequestStatus){
form['mergeId'] = data.insertedRequestId;
}else{
form['mergeId'] = null;
}
});
I am writing a custom attribute to require a property in a viewmodel if another property has a specified value.
I used this post for reference: RequiredIf Conditional Validation Attribute
But have been encountering issues with the .NET Core revisions for IClientModelValidator. Specifically, the server side validation works as expected with ModelState.IsValid returning false, and ModelState errors containing my custom error codes. I feel that I am missing something when translating between the differing versions of validator.
The old (working) solution has the following:
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata,
ControllerContext context)
{
var rule = new ModelClientValidationRule
{
ErrorMessage = ErrorMessageString,
ValidationType = "requiredif",
};
rule.ValidationParameters["dependentproperty"] =
(context as ViewContext).ViewData.TemplateInfo.GetFullHtmlFieldId(PropertyName);
rule.ValidationParameters["desiredvalue"] = DesiredValue is bool
? DesiredValue.ToString().ToLower()
: DesiredValue;
yield return rule;
}
Based on the changes to IClientModelValidator outlined here: https://github.com/aspnet/Announcements/issues/179 I have written the following methods:
public void AddValidation(ClientModelValidationContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
MergeAttribute(context.Attributes, "data-val", "true");
var errorMessage = FormatErrorMessage(context.ModelMetadata.GetDisplayName());
MergeAttribute(context.Attributes, "data-val-requiredif", errorMessage);
MergeAttribute(context.Attributes, "data-val-requiredif-dependentproperty", PropertyName);
var desiredValue = DesiredValue.ToString().ToLower();
MergeAttribute(context.Attributes, "data-val-requiredif-desiredvalue", desiredValue);
}
private bool MergeAttribute(
IDictionary<string, string> attributes,
string key,
string value)
{
if (attributes.ContainsKey(key))
{
return false;
}
attributes.Add(key, value);
return true;
}
These are being called as expected, and values are properly populated, yet the following JS is ignored. Leaving me to suspect I am missing something between the two.
$.validator.addMethod("requiredif", function (value, element, parameters) {
var desiredvalue = parameters.desiredvalue;
desiredvalue = (desiredvalue == null ? "" : desiredvalue).toString();
var controlType = $("input[id$='" + parameters.dependentproperty + "']").attr("type");
var actualvalue = {}
if (controlType === "checkbox" || controlType === "radio") {
var control = $("input[id$='" + parameters.dependentproperty + "']:checked");
actualvalue = control.val();
} else {
actualvalue = $("#" + parameters.dependentproperty).val();
}
if ($.trim(desiredvalue).toLowerCase() === $.trim(actualvalue).toLocaleLowerCase()) {
var isValid = $.validator.methods.required.call(this, value, element, parameters);
return isValid;
}
return true;
});
$.validator.unobtrusive.adapters.add("requiredif", ["dependentproperty", "desiredvalue"], function (options) {
options.rules["requiredif"] = options.params;
options.messages["requiredif"] = options.message;
});
Any ideas?
EDIT: Just to erase doubt that the server side is working properly and the issue almost certainly lies client side, here is a snip of the generated HTML for a decorated field:
<input class="form-control" type="text" data-val="true" data-val-requiredif="Profession Other Specification is Required" data-val-requiredif-dependentproperty="ProfessionTypeId" data-val-requiredif-desiredvalue="10" id="ProfessionOther" name="ProfessionOther" value="" placeholder="Please Specify Other">
So I had the same setup and same result as the original questioner. By stepping through a project where custom validators were being fired and where they weren't, I was able to determine that when the page is initially loaded, jquery.validate.js attaches a validator object to the form. The validator for the working project contained the key for the custom validator I had created. The validator for the one that did not work was missing that key (which was later added and available at the time I was posting my form).
Unfortunately, as the validator object had already been created and attached to the form without my custom validator, it never reached that function. The key to solving this issue was to move my two JS functions outside of the jQuery ready function, as close to the top of my main script as possible (just after I set my jQuery validator defaults). I hope this helps someone else!
My project is written in TypeScript, so my structure is a bit different but the JavaScript for actually adding the validator remains unchanged.
Here is the code for my "SometimesRequired" validator Typescript class:
export class RequiredSometimesValidator {
constructor() {
// validator code starts here
$.validator.addMethod("requiredsometimes", function (value, element, params) {
var $prop = $("#" + params);
// $prop not found; search for a control whose Id ends with "_params" (child view)
if ($prop.length === 0)
$prop = $("[id$='_" + params + "']");
if ($prop.length > 0) {
var ctrlState = $prop.val();
if (ctrlState === "EditableRequired" && (value === "" || value === "Undefined"))
return false;
}
return true;
});
$.validator.unobtrusive.adapters.add("requiredsometimes", ["controlstate"], function (options) {
options.rules["requiredsometimes"] = options.params["controlstate"];
options.messages["requiredsometimes"] = options.message;
});
// validator code stops here
}
}
Then in my boot-client.ts file (the main file which powers my application's JavaScript), I instantiate a new copy of the validator above (thus calling the constructor which adds the custom validator to the validator object in memory) outside of document.ready:
export class Blueprint implements IBlueprint {
constructor() {
// this occurs prior to document.ready
this.initCustomValidation();
$(() => {
// document ready stuff here
});
}
private initCustomValidation = (): void => {
// structure allows for load of additional client-side validators
new RequiredSometimesValidator();
}
}
As a very simple example not using TypeScript, you should be able to do this:
<script>
$.validator.addMethod("requiredsometimes", function (value, element, params) {
var $prop = $("#" + params);
// $prop not found; search for a control whose Id ends with "_params" (child view)
if ($prop.length === 0)
$prop = $("[id$='_" + params + "']");
if ($prop.length > 0) {
var ctrlState = $prop.val();
if (ctrlState === "EditableRequired" && (value === "" || value === "Undefined"))
return false;
}
return true;
});
$.validator.unobtrusive.adapters.add("requiredsometimes", ["controlstate"], function (options) {
options.rules["requiredsometimes"] = options.params["controlstate"];
options.messages["requiredsometimes"] = options.message;
});
$(function() {
// document ready stuff
});
</script>
The key to solving this issue was to move my two JS functions outside of the jQuery ready function, as close to the top of my main script as possible (just after I set my jQuery validator defaults). I hope this helps someone else!
Credit goes to #Loni2Shoes
I am using Firebase and AngularFire to try to keep my dat synced. My code is:
var questionsRef, ref, ref1;
questionsRef = fbase.child("questions").orderByChild("subject").equalTo("english");
$scope.questions = $firebaseArray(questionsRef);
$scope.questions.$loaded().then(function() {
if ($stateParams.id) {
$scope.currentQuestion = $scope.questions.$getRecord($stateParams.id);
return console.log($scope.currentQuestion);
} else {
return $scope.currentQuestion = {
answers: []
};
}
});
$scope.currentQuestion.editedByUserId = (ref = auth.$getAuth()) != null ? (ref1 = ref.auth) != null ? ref1.uid : void 0 : void 0;
$scope.currentQuestion.editedOn = Firebase.ServerValue.TIMESTAMP;
$scope.currentQuestion.$save();
This should save the updates to the currentQuestion (based on the form in HTML via ng-model), but instead I get Error: $scope.currentQuestion.$save is not a function. (In '$scope.currentQuestion.$save()', '$scope.currentQuestion.$save' is undefined)
What am I doing wrong?
Argh - that was silly. Turns out I was using it wrong. I need to do
$scope.questions.$save($scope.currentQuestion)
Since the$save is on the questions array