Meteor embedded documents reactivity - javascript

I have a bootstrap modal that will display info about a parent document. The modal then has next/prev buttons that can rotate around a list of parent documents and reactively change the info displayed on the modal. It does this by simply changing a ReactiveVar of the current parent's onClick of next/prev. This works like a charm.
My problem is that each parent shows some child data as well. The child data is an array of embedded documents. And each document's property has some HTML inputs. I'm trying to pre-populate the values of the html inputs. However, I'm finding this does not work. id and foo are properties on the child document(s).
A call to {{parent}} is a Helper method that returns the current parent from MiniMongo. This seems to work fine as the parent's metadata changes reactively.
{{#each parent.childrenArray}}
<input type="text" id="{{id}}" value="{{foo}}">
{{/each}}
So the issue is that the HTML value does not change. Clicking next will still show the old child's value. I understand there's no reactivity for embedded documents and I'm sure this is the problem. Strangely, upon inspecting, I am in fact seeing the input's id being changed though. Does anybody know the solution to this? Thanks!
This seems to happen when I try to save the children back to the DB. I.e. hitting next/prev should save whatever value users puts into the HTML input before moving on to the next Parent and pre-loading its children

If parent is a helper, then I don't think you can use the subscript notation to get its properties, at least not reactively. However you should be able to use this instead:
{{#with parent}}
{{#each childrenArray}}
<input {{attributes}}>
{{/each}}
{{/with}}
Update: The other thing that is probably required is to use an attribute helper. I've also updated the code above to use this.
Template.yourTemplateName.helpers({
attributes() {
return {
type: 'text',
id: this.id,
value: this.foo
};
}
});
Update 2:
Working "offline" in a git repo by the OP I realized that the modal body was better done as a separate template, such that the data context for that template would change reactively. The part of the code that was at odds is not represented in the question, so read this question and answer with a grain of salt.

Related

Use v-model on a non-input element and bind it to the innerHTML value

Background info
We use a custom directive to deal with localization. It works by providing a content id to the directive like so:
<div v-content="'text.to.translate'"></div>
The directive creates a mapping of all elements which implement the directive and which content id they correspond to. The directive then performs a request using this mapping to fetch all of the translated strings needed to display on the page. Once the request comes back successfully, the response looks like this:
response: {
'text.to.translate': 'Text to translate',
'some.other.text': 'Some other text that is translated'
}
This response is joined with the mapped elements like so:
mappedElements.forEach(mappedElem => {
mappedElem.htmlElem.innerHTML = response[mappedElem.contentId]
});
The corresponding HTML tags would then render as
<div>Text to translate</div>
This all happens outside of components and happens asynchronously once the page is created. There is a use case where I show fetched data in a table, and one of the columns will always correspond to a set list of ~10 strings. However, the data that is returned for this column are the content ids of the strings, rather than the strings themselves. This data would always be fetched after the translated contents were already returned, and for reasons, I do not want to create a separate request with just those content values. I would like to include the 10 strings in the initial content request. For this reason, I'm going to create 10 dummy elements with those specific content id values that will always be part of the initial content fetch.
I made a component that accepts a list of contentIds and display them in the template like so:
<div v-for="content in contentIds">
<span v-content="content.id"></span>
</div>
I would like to return the translated strings in a list once the values are returned and populated inside of the <spans>. Given that I have no reference to the request being performed within my component, I cannot just simply use a callback to map the data.
My question
I need to basically use the equivalent of a v-model, but on a <span> element, where an element's innerHTML is bound to a variable. If the innerHTML value is changed outside of the component where that element is defined, I still need the component to see the change and be able to update the variable reactively.
Is there a way to do this?
I know this is old, but for anyone who finds it, you can emit an 'input', it works for both form and non-form elements:
ParentComponent:
<ChildComponent v-model="foo"/>
ChildComponent:
<div #click="$emit('input', 'bar')">Button</div>

How to change the attribute of an input field in the DOM

I want to use the jquery plugin "datedropper". Datedropper
To set a default date, the plugin uses an attribute on the input field:
<input id="addjobstartdate" data-default-date="CURRENT DATE"/>
I want to change the default date dynamically, so I try to use:
document.getElementById("addjobstartdate").setAttribute('data-default-date', getjobdetails[31]);
or
$('#addjobstartdate').attr('data-default-date', getjobdetails[31]);
But when I look at the source-code of the page, neither of the commands changes the DOM.
data-default-date is still "CURRENT DATE". Can someone please explain this to me.
If you want to see the changes in the DOM, this worked for me:
document.getElementById("addjobstartdate").dataset.defaultDate = "TODAY"
<input id="addjobstartdate" data-default-date="CURRENT DATE"/>
Dataset documentation on MDN
When changing attrs on HTML elements via client side code, the changes are not reflected in the source code or the dev tools. Try running $('#addjobstartdate').attr('data-default-date') in the console to confirm the value.
EDIT:
I stand corrected. Dev tools does update the value. Not sure if this changed. Your code seems correct.
The "source code" is not the same thing as the "Document Object" that is currently loaded into memory. All you will ever see when looking at the source code is the original code that was delivered to the client machine for processing.
Once the client modifies that, the changes persist only in memory. You can see those changes by looking in your Developer's Tools (press F12 in your browser) and click on the Element tab. This tab shows you the code that is currently in memory (also known as the DOM or Document Object Model).
Now, when working with DOM objects in JavaScript, there are two ways to affect the current state of an element and it's important to understand the effects of working with these two techniques:
Work with the element's current HTML state (which is done with
setAttribute(), removeAttribute(), and getAttribute()). These types of changes will be visible when looking at the DOM in the Elements tab.
Work with element's current DOM Object state (which is done with the JavaScript property that represents the current state) of the element in memory. These types of changes will not cause any change to the markup shown in the Elements tab, but will affect the element by persisting in memory.
Most importantly, the property value can be different than the attribute value. This can be confusing but the HTML state is what the element looks like from the outside and the property state is what is really happening on the inside, like you can put a happy face on so that people who look at you think your happy (the HTML state), but you might actually be sad for real (the property state).
When the property state hasn't been set, the attribute state is all that matters and will have total control over the state of the element. When the property state is set, it overrides whatever the attribute state may be and controls the actual state of the element.
// Get a reference to the button
var btn = document.querySelector("[type=button]");
// Test what the current HTML state is:
console.log("disabled attribute state: ", btn.getAttribute("disabled"));
// Test what the current mapped property state is:
console.log("disabled property state: ", btn.disabled);
console.log("...changing button's disabled PROPERTY to false...");
// Change the property state, which will override the HTML state and
// and cause it to become enabled.
btn.disabled = false;
// Test what the current HTML state is:
console.log("disabled attribute state: ", btn.getAttribute("disabled")); // null because property overrode HTML
// Test what the current mapped property value is:
console.log("disabled property state: ", btn.disabled);
<input type="button" value="I'm disabled" disabled="disabled">

How can I manipulate HTML elements which are added during the runtime?

The code im working on first makes a call to the database. Through this call, it is determined wether there are available workstations in the office or not.
If there are available workstations, "option" elements are added to a "select" element. This is achieved via jquery:
$('#idofselectelement').html(data)
Where "data" represents the markup to be inserted into the "select" element.
Now, my problem is that I'm trying to implement some code which checks wether your "favorite workstation" is available in the selected timeframe and then automatically preselects the respective workstation from the dropdownmenu in the "select" element. Everything is working so far, except for the selection of the workstation from the dropdown menu :/
The part of
I'm rather new to programming with javascript and the HTML DOM, so I'm not sure whether the fact that the options im trying to chose from are added during the runtime?
The code I've tried to manipulate the dropdown menu with is like this:
$('#idofselectelement').val(favoriteworkstation);
However, as I said, this doesn't work.
I've also already tried to output (console.log) the select element's length property right after the code which adds the markup with the available options has run.
But according to the output Im getting, the length is zero Oo
However, the dropdownmenu is definitely being created AND I can indeed manipulate it, but unfortunately not in the way I want to.
If I add an onclick event which sets the value of the respective select element, then the value in the select field indeed changes to the value specified in the event handler.
So I wonder why I can't have the favorite workstation preselected after the timeframe was chosen...
EDIT:
For further insight into the problem, I'm adding a bit more code here.
This is what the HTML Select element looks like BEFORE anything is added during the runtime:
<label for="#sitz">Sitz Nr.</label>
<select type="text" class="form-control" id="sitz" name="sitz" value="">
the markup which is added during the runtime
<option>workstationvalue</option>
<option>workstationvalue</option>
//and so on, depending on the situation...
This is a timing issue.
The js trying to find the element is faster than the actual add of the element to DOM.
Can you describe what you want to do? You might be able to do that before adding the element to DOM.
Editing before adding to DOM is possible if you convert your String to an jQuery object
var $jqueryObject = $(data);
$jqueryObject.find('.classYouSearch').val(value);
$('.whereToAd').html($jqueryObject);

Global Method to Clone object in vuejs rather then reference it, to avoid code duplication

I am building simple CRUD div list with Vuejs, which allows me to perform all operations on single page as search through results by the means of filter.
The problem I face is that in order to do perform update I have to have two containers for each data object in the loop.
So 1st container acts like view, 2nd has the form with same values as view for editing purposes. I basically toggle them to switch from view to edit mode
It looks something like that:
<input v-model="search">
<items v-for="product in products | filterBy search in searchFields">
<item>
<div v-show="!edit">
Display Data "div"
<div>
<div v-show="edit">
Edit Data "form"
<div>
<item>
</items>
I have a problem with using "filterBy" when looping through items to search through them. Because data in 1st and 2nd container is bound upon editing field in edit mode the element disappears. As filter does not care whether the item is in edit mode or in view mode. It simply filters items, and when the value of item is changed and no longer equal to search value its gone.
I have solved the problem by simply cloning object of view element and passed cloned object to form element instead of referencing it by adding objectClone method on child item. Which sort of fixes my problem as the data in edit mode no longer bound to view mode. But this creates another problem, now I have to copy objectClone method on each new child element of the loop in order for this to work. Is there a way to clone element within vuejs without creating a dedicated method or is it possible to create global method objectClone to use it across all Vue elements?
Additional problem I get with my approach is that element after editing now cloned and I cant remove it from parent data container. As it no longer references the previous element that got cloned.

Emberjs handlebars content binding edit field not working

I have an emberJS object, viewed by an editablefield view, in order to be able to edit it with bindings.
I modify the view in order to be able to replace links in the text.
My problem is that if I use the inside views (Zs.RealValue) render function, it won't refresh on each update, like the simple {{value}} would do, only if I erase the text and after erase the first change.
I have a simple example here:
Steps to do: double click on one of the edit fields, the edit box appears, try modify the value, you will see, that the simple view is being updated, but not the other text view.
http://jsfiddle.net/symunona/hCjEc/7/
The problem appears because you try to implement the render function yourself in RealValue. If you change RealValue to just this:
Zs.RealValue = Ember.View.extend({
template: Ember.Handlebars.compile("{{value}}")
});
then the example works.

Categories

Resources