My app allows the users to manage their documents. When creating one, a user has to either enter the document content manually or select a file from their computer (which would convert many formats to HTML for the user).
Currently, I have a simple FileUploaderView which is basically an <input type="file"> that listens to file changes, and updates the value property of the view with an object like { file: { type: SOME_TYPE' }, content: SOME_CONTENT }.
Then, DocumentsNewController listens to changes in it and converts supported files to HTML, and puts the result into the document body.
However, doing it this way feels simply wrong and does not allow for simple reuse (which I want to be able to do).
App.DocumentsNewController = Ember.ObjectController.extend
# ... stuff ...
handleDocumentUpload: (->
doc = #get 'documentUpload'
return unless doc
Ember.run =>
#set 'uploadError', false
#set 'unsupportedFile', false
#set 'processingUpload', true
type = doc.file.type
text = ''
try
if type.match /^text\//
text = doc.content
# Convert new lines to br's and paragraphs
text = '<p>' + text.replace(/\n([ \t]*\n)+/g, '</p><p>').replace('\n', '<br />') + '</p>'
else if type == 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
text = new DOCX2HTML(doc.content).convert()
else
#set 'unsupportedFile', true
catch error
#set 'uploadError', true
finally
#set 'text', text
Ember.run => #set 'processingUpload', false
).observes 'documentUpload'
And the template is something like
... stuff ...
{{view App.FileUploaderView valueBinding="documentUpload" accept="text/*"}}
What would be the proper way to refactor file converting stuff out of the controller?
I want to be able to do something like:
{{documentHandler resultBinding="documentUpload"}}
and in controller
App.DocumentsNewController = Ember.ObjectController.extend
# ... stuff ...
handleDocumentUpload: (->
if doc = #get 'documentUpload'
#set 'text', doc
).observes 'documentUpload'
My first thought was to make a DocumentHandlerView which would display the input field, show the spinner, show the errors, parse the document and assign the result to result (and since controller's template has resultBinding="documentUpload", the HTML would trigger the controller's observer).
Using a view for that would allow for easier reuse but I still feel it's not the view's job to parse the document.
Is there a better way?
After reading closely your question the best thing that comes in mind would be to create a Ember.Mixin and then use it for all the controllers that need the same functionality.
Example taken from the ember API docs:
App.Editable = Ember.Mixin.create({
edit: function() {
console.log('starting to edit');
this.set('isEditing', true);
},
isEditing: false
});
// Mix mixins into classes by passing them as the first arguments to
// .extend.
App.CommentView = Ember.View.extend(App.Editable, {
template: Ember.Handlebars.compile('{{#if isEditing}}...{{else}}...{{/if}}')
});
commentView = App.CommentView.create();
commentView.edit(); // outputs 'starting to edit'
The example is only conceptual, but it will be easy to create a mixin yourself and put all the common logic in there.
Hope it helps.
Related
I am very new to testing and I'm struggling my way through all this new stuff I am learning. Today I want to write a test for a vuetify <v-text-field> component like this:
<v-text-field
v-model="user.caption"
label="Name"
:disabled="!checkPermissionFor('users.write')"
required
/>
my test should handle the following case:
an active, logged in user has a array in vuex store which has his permissions as a array of strings. exactly like this
userRights: ['dashboard', 'imprint', 'dataPrivacy']
the checkPermissionFor() function is doing nothing else then checking the array above with a arr.includes('x')
after it came out the right is not included it gives me a negotiated return which handles the disabled state on that input field.
I want to test this exact scenario.
my test at the moment looks like this:
it('user has no rights to edit other user overview data', () => {
const store = new Vuex.Store({
state: {
ActiveUser: {
userData: {
isLoggedIn: true,
isAdmin: false,
userRights: ['dashboard', 'imprint', 'dataPrivacy']
}
}
}
})
const wrapper = shallowMount(Overview, {
store,
localVue
})
const addUserPermission = wrapper.vm.checkPermissionFor('users.write')
const inputName = wrapper.find(
'HOW TO SELECT A INPUT LIKE THIS? DO I HAVE TO ADD A CLASS FOR IT?'
)
expect(addUserPermission).toBe(false)
expect(inputName.props('disabled')).toBe(false)
})
big questions now:
how can I select a input from vuetify which has no class like in my case
how can I test for "is the input disabled?"
wrapper.find method accepts a query string. You can pass a query string like this :
input[label='Name'] or if you know the exact index you can use this CSS query too : input:nth-of-type(2).
Then find method will return you another wrapper. Wrapper has a property named element which returns the underlying native element.
So you can check if input disabled like this :
const buttonWrapper = wrapper.find("input[label='Name']");
const isDisabled = buttonWrapper.element.disabled === true;
expect(isDisabled ).toBe(true)
For question 1 it's a good idea to put extra datasets into your component template that are used just for testing so you can extract that element - the most common convention is data-testid="test-id".
The reason you should do this instead of relying on the classes and ids and positional selectors or anything like that is because those selectors are likely to change in a way that shouldn't break your test - if in the future you change css frameworks or change an id for some reason, your tests will break even though your component is still working.
If you're (understandably) worried about polluting your markup with all these data-testid attributes, you can use a webpack plugin like https://github.com/emensch/vue-remove-attributes to strip them out of your dev builds. Here's how I use that with laravel mix:
const createAttributeRemover = require('vue-remove-attributes');
if (mix.inProduction()) {
mix.options({
vue: {
compilerOptions: {
modules: [
createAttributeRemover('data-testid')
]
}
}
})
}
as for your second question I don't know I was googling the same thing and I landed here!
This is a follow on to "APEX row selector" posted 5 days ago.
The problem was collecting multiple values from an interactive grid. From the excellent links to post supplied I was able to achieve this. However, the next part of the project is to open an edit dialog page and update multiple values.
I added this code to the attribute of the interactive grid:
function (config)
{
var $ = apex.jQuery,
toolbarData = $.apex.interactiveGrid.copyDefaultToolbar(),
toolbarGroup = toolbarData.toolbarFind("actions3");
toolbarGroup.controls.push(
{
type: "BUTTON",
action: "updateCar",
label: "Edit Selected Cars",
hot: true,
});
config.toolbarData = toolbarData;
config.initActions = function (actions)
{
// Defining the action for activate button
actions.add(
{
name: "updateCar",
label: "Edit Selected Cars",
action: updateCar
});
}
function updateCar(event, focusElement)
{
var i, records, model, record,
view = apex.region("ig_car").widget().interactiveGrid("getCurrentView");
var vid = "";
model = view.model;
records = view.getSelectedRecords();
if (records.length > 0)
{
for (i = 0; i < records.length; i++)
{
record = records[i];
alert("Under Development " + record[1]);
vid = vid + record[1] + "||";
apex.item("P18_CAR").setValue(vid);
// need to open next page here and pass parameters
}
}
}
return config;
}
I need to know how to open a form and have the parameter values available to pass to an oracle update script.
Thank you for any help you can provide. I did find some posts but I really need a good example. I have tried everything to no avail.
There are various ways you could do this. Here's one way, perhaps someone else will offer a more efficient option.
The JavaScript options for navigation in APEX are documented here:
https://docs.oracle.com/en/database/oracle/application-express/19.1/aexjs/apex.navigation.html
Since you're trying to open a separate page, you probably want to use apex.navigation.dialog, which is what APEX automatically uses when opening modal pages from reports, buttons, etc.
However, as noted in the doc, the URL for the navigation must be generated server-side for security purposes. You need a dynamic URL (one not known when the page renders), so you'll need a workaround to generate it. Once you have the URL, navigating to it is easy. So how do you get the URL? Ajax.
Create an Ajax process to generate the URL
Under the processing tab of the report/grid page, right-click Ajax Callback and select Create Process.
Set Name to GET_FORM_URL.
Set PL/SQL code to the following
code:
declare
l_url varchar2(512);
begin
l_url := apex_page.get_url(
p_application => :APP_ID,
p_page => 3,
p_items => 'P3_ITEM_NAME',
p_values => apex_application.g_x01
);
apex_json.open_object();
apex_json.write('url', l_url);
apex_json.close_object();
end;
Note that I'm using apex_item.get_url to get the URL, this is an alternative to apex_util.prepare_url. I'm also using apex_json to emit JSON for the response to the client.
Also, the reference to apex_application.g_x01 is important, as this will contain the selected values from the calling page. You'll see how this was set in the next step.
Open the URL with JavaScript
Enter the following code in the Function and Global Variable Declaration attribute of the calling page:
function openFormPage(ids) {
apex.server.process(
'GET_FORM_URL',
{
x01: ids.join(':')
},
{
success: function (data) {
var funcBody = data.url.replace(/^"javascript:/, '').replace(/\"$/,'');
new Function(funcBody).call(window);
},
error: function (jqXHR, textStatus, errorThrown) {
console.error(errorThrown);
// handle error
}
}
);
}
In this case, I'm using apex.server.process to call the server-side PL/SQL process. Note that I'm passing the value of ids.join(':') to x01. That value will become accessible in the PL/SQL code as apex_application.g_x01. You can use additional items, or you can pass a colon-delimited string of values to just one item (as I'm doing).
The URL that's returned to the client will not be a standard URL, it will be a JavaScript snippet that includes the URL. You'll need to remove the leading and trailing parts and use what's left to generate a dynamic function in JavaScript.
This is generally frowned upon, but I believe it's safe enough in this context since I know I can trust that the response from the process call is not malicious JavaScript code.
Add a security check!!!
Because you're creating a dynamic way to generate URLs to open page 3 (or whatever page you're targeting), you need to ensure that the modal page is protected. On that page, create a Before Header process that validates the value of P3_ITEM_NAME. If the user isn't supposed to be able to access those values, then throw an exception.
I am creating a mixin to validate components with texFields it looks like this, I am using Ember.defineProperty to create a cp on the fly with a dynamic dependant key:
App.ValidationMixin = Ember.Mixin.create
classNameBindings: ['isInvalid']
input: (e) ->
#_super.apply this, arguments
setup: Ember.on 'didInsertElement', ->
unless validations = #get('validations')
el = #autocompleteElement()
# I had to add this to access the prop rather than it getting
# triggered when the dynamic property changes
if #get('isInvalid')
el.addClass 'is-invalid'
else
el.removeClass 'is-invalid'
validationMixin: Ember.on 'didInsertElement', ->
unless validations = #get('validations')
return
dynamicProperty = # logic to determine dynamic property
Ember.defineProperty this, 'isInvalid', Ember.computed dynamicProperty, 'validator.isSubmitted', ->
# validation logic
The problem is, I have to manually check for this.get('isInvalid') rather then the property function being executed when one of the dependant keys changes.
Can anyone explain why this is?
Ember probably thinks that the property 'isInvalid' is not used anywhere and therefore doesn't update it. I guess that 'didInsertElement' is called after the classNameBindings property... Try to output the 'isInvalid' property in the template to check if that's the case because therefore it must be updated.
I'm trying to pass some data along to the autocomplete_light.AutocompleteModelBase so I can exclude some models from the search. I'm trying to use the Dependencies info in the docs here
but I can seem to get it.
The id of the input is id_alternate_version-autocomplete, so I'm trying:
$("#id_alternate_version-autocomplete").yourlabsWidget().autocomplete.data = {'id': 'foo'};
But the url called looks like http://127.0.0.1:8000/autocomplete/FooAutocomplete/?q=bar
I want: http://127.0.0.1:8000/autocomplete/FooAutocomplete/?q=bar&id=foo
How can I do something like that?
DAL provides a way to do this with "forwarding" of another rendered form field's value.
See http://django-autocomplete-light.readthedocs.io/en/master/tutorial.html#filtering-results-based-on-the-value-of-other-fields-in-the-form
This is how I did it:
$(document).ready(function() {
$('form#recipe').on('change propertychange keyup input paste', function() {
var ingredient_item_type = $("form#recipe input[type='radio']:checked").val();
var widget = $("form#recipe input#id_ingredients_text").parents('.autocomplete-light-widget');
if(ingredient_item_type) {
widget.yourlabsWidget().autocomplete.data['hello'] = 'world';
}
});
});
Javascript acrobatics aside, the key observation is thus:
anything you put in the .data object of the autocomplete widget will
automatically be made part of the GET request. HTH.
Hello StackOverflow experts,
I would like to know if it would be possible to use Ember.js' computed properties to modify the value of the property before returning to whatever object requests it.
Imagine this simple example:
I have a User object with mail property
When I set the property, I want the email address to change from first.last#example.com to first.last#anotherexample.com, then return it
When I request the property ( via User.get ) I want to get the modified property back.
I think it should be pretty simple by utilising another 'helper' property, like formatted_mail, where I would store and retrieve the formatted value, but I wonder if something like this can be done without additional model properties.
So far, I have this coffescript code, but I always get 'undefined' when reading the property, even though I set it before, so I suspect the value does not get saved by Ember anywhere:
mail: ( ( key, value ) ->
if arguments.length == 1
return this.get 'mail'
else
return value.split( '#' )[0] + '#anotherexample.com'
).property 'mail'
Thank you for your assistance!
You are close to solution.
As computed properties are always cached by default in Ember (you could disable this behaviour using .volatile()), you do not have to specify what to do when arguments.length is 1, except if you want to specify a default value.
So here it should looks like:
App.User = Ember.Object.extend({
mail: function(key, value) {
if (arguments.length === 2) {
return value.split('#')[0] + "#tieto.com";
}
return null;
}.property()
});
The return null just specify the default value.
When you set the mail property, it will cache the returned value and always returns it without recomputing this property.
Note that you can do that only because the mail property does not depend on other properties. If you were declaring it with .property('anotherProperty'), the mail property will be recomputed any time anoterProperty changes. So in the example above it will reset it to null.
You can try it in this JSFiddle.