<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Juliana Louback, software eng.</title>
    <description>An informal account of my adventures in coding, including scattered moments of genius as well as my more common stupidities. I intend to cover a variety of themes.</description>
    <link>http://julianalouback.com/</link>
    <atom:link href="http://julianalouback.com/feed.xml" rel="self" type="application/rss+xml" />
    <pubDate>Sat, 11 Nov 2017 16:29:31 +0000</pubDate>
    <lastBuildDate>Sat, 11 Nov 2017 16:29:31 +0000</lastBuildDate>
    <generator>Jekyll v3.6.2</generator>
    
      <item>
        <title>NLP: Part-Of-Speech Tagging with CYK</title>
        <description>&lt;p&gt;During my MSc program, I was lucky to squeeze into &lt;a href=&quot;http://www.cs.columbia.edu/~mcollins/&quot;&gt;Michael Collin&lt;/a&gt;’s NLP class. We used his &lt;a href=&quot;https://www.coursera.org/course/nlangp&quot;&gt;Coursera course&lt;/a&gt; as part of the program, which I’d highly recommend.&lt;/p&gt;

&lt;p&gt;Recently I decided to review my NLP studies and I believe the best way to learn or relearn a subject is to teach it. This is the second in a series of 4 posts with a walk-through of the algorithms we implemented during the course. I’ll provide links to my code hosted on Github.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclaimer: Before taking this NLP course, the only thing I knew about Python was that ‘it’s the one without curly brackets’. I learned Python on the go while implementing these algorithms. So if I did anything against Python code conventions or flat-out heinous, I apologize and thank you in advance for your understanding. Feel free to write and let me know.&lt;/em&gt;&lt;/p&gt;

&lt;h2 id=&quot;the-concept&quot;&gt;The Concept&lt;/h2&gt;

&lt;p&gt;The Cocke–Younger–Kasami algorithm is used to parse context free grammars. A context free grammar (CFG) contains a set of rules that describe a [formal] language. For example, for the English language we would set a rule that says an article is always followed by a noun or an adjective(s) + noun combo. A context free grammar has to have enough rules to cover all the possible variations in the language. The rules can be one -&amp;gt; one, one -&amp;gt; many or one -&amp;gt; stop. There are two types of building blocks for these rules; &lt;em&gt;non-terminals&lt;/em&gt; and &lt;em&gt;terminals&lt;/em&gt;. A non-terminal contains one or more terminals, for example, a nominal phrase. A nominal phrase contains a noun and maybe also an article and/or adjectives. The noun, article, and adjectives are all &lt;em&gt;terminals&lt;/em&gt;. So for the sentence &lt;em&gt;“The dog barked.”&lt;/em&gt; we could establish the following rules:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;S -&amp;gt; NP VP&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;NP -&amp;gt; DET N&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;VP -&amp;gt; V&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;DET -&amp;gt; The&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;NOUN -&amp;gt; dog&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;VERB -&amp;gt; barked&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Where &lt;em&gt;S&lt;/em&gt; is the sentence (or rather, the start symbol of the sentence), &lt;em&gt;NP&lt;/em&gt; is the nominal phrase, &lt;em&gt;VP&lt;/em&gt; is the verbal phrase, &lt;em&gt;DET&lt;/em&gt; a determinant, &lt;em&gt;NOUN&lt;/em&gt; a noun, &lt;em&gt;VERB&lt;/em&gt; a verb.&lt;/p&gt;

&lt;p&gt;These rules can create a part-of-speach parse tree for any given sentence providing an understanding of how different parts of the sentence interact with and affect each other. This can be useful for machine translation, information extraction and other scenarios. However, there are cases where there is more than one grammatically correct parsing option. Take the sentence &lt;em&gt;“He fed her dog food”&lt;/em&gt;. This could be interpreted as (A) A man feeding a woman’s dog some food or (B) A man feeding a woman some dog food. A human would expect option (A) to be the correct interpretation (hopefully?), but would an algorithm?&lt;/p&gt;

&lt;p&gt;The CYK algorithm enriches the CFG by adding a parameter &lt;em&gt;q(X -&amp;gt; Y)&lt;/em&gt; that could be described as the probability of choosing rule &lt;em&gt;X -&amp;gt; Y&lt;/em&gt; (as opposed to some other rule &lt;em&gt;X -&amp;gt; Z&lt;/em&gt;) given that we are currently at &lt;em&gt;X&lt;/em&gt;. The probability of a parse tree is the product of all &lt;em&gt;q&lt;/em&gt; parameters for the rules that compose the tree. Given these &lt;em&gt;q&lt;/em&gt; parameters, the CYK algorithm chooses the most likely parse tree out of all possible parse trees. Note that a parse tree has a cascading effect, you may chose a rule &lt;em&gt;X -&amp;gt; Y&lt;/em&gt; because it has a higher &lt;em&gt;q&lt;/em&gt; parameter than &lt;em&gt;X -&amp;gt; Z&lt;/em&gt; but then the subtree starting from &lt;em&gt;Y&lt;/em&gt; may not have a valid rule to fit the sentece (&lt;em&gt;q&lt;/em&gt; parameter 0) or is much less likely than a subree starting from &lt;em&gt;Z&lt;/em&gt;. In that case, the rule &lt;em&gt;X -&amp;gt; Z&lt;/em&gt; would have been the best choice. In other words, it’s not enough to just use a “greedy” technique and always choose the rule with the highest &lt;em&gt;q&lt;/em&gt; parameter.&lt;/p&gt;

&lt;h2 id=&quot;the-requirements&quot;&gt;The Requirements&lt;/h2&gt;

&lt;p&gt;Training data: The file &lt;a href=&quot;https://github.com/JLouback/nlp-CKY/blob/master/parse_train.dat&quot;&gt;parse_train.dat&lt;/a&gt; provided by prof. Michael Collins (from the Wall St. Journal corupus) has a series of sentences already tagged with their respective POS and parsed into a tree. Each line is one tree, each level of a tree separated by square braquets.&lt;/p&gt;

&lt;p&gt;Development data: The file &lt;a href=&quot;https://github.com/JLouback/nlp-CKY/blob/master/parse_dev.dat&quot;&gt;parse_dev.dat&lt;/a&gt; provided by prof. Michael Collins has a list of sentences (one per line).&lt;/p&gt;

&lt;p&gt;Script for rule count: The file &lt;a href=&quot;https://github.com/JLouback/nlp-viterbi/blob/master/count_cfg_freqs.py&quot;&gt;count_cfg_freqs.py&lt;/a&gt; provided by prof. Michael Collins contains a script to produce the rule counts.&lt;/p&gt;

&lt;p&gt;Training data rule count: Run &lt;strong&gt;python count cfg freqs.py parse train.dat &amp;gt; cfg.counts&lt;/strong&gt; to obtain the list of rule counts from the training data. The format is &lt;strong&gt;count ruleType rule&lt;/strong&gt; separated by tabs. Rules can be &lt;em&gt;unary&lt;/em&gt; (example: &lt;em&gt;V -&amp;gt; run&lt;/em&gt;), &lt;em&gt;binary&lt;/em&gt; (example: &lt;em&gt;Sentence -&amp;gt; NP VP&lt;/em&gt;) or &lt;em&gt;nonterminal&lt;/em&gt; (list of acronyms representing nonterminals, example: &lt;em&gt;NP&lt;/em&gt;).&lt;/p&gt;

&lt;h2 id=&quot;the-algorithm&quot;&gt;The Algorithm&lt;/h2&gt;
&lt;p&gt;Python code: &lt;a href=&quot;https://github.com/JLouback/nlp-CKY/blob/master/cyk.py&quot;&gt;cyk.py&lt;/a&gt;
Usage: python cyk.py cfg_vert.counts parse_dev.dat cfg.counts &amp;gt; [output_file]
Summary:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Optional Preprocessing&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Re-label words in training data with frequency &amp;lt; 5 as ‘&lt;em&gt;RARE&lt;/em&gt;’ - This isn’t required, but useful to increase precision. If used, perform Step 1 (see below) then re-label the words to redo Step 1.&lt;/p&gt;

&lt;p&gt;Python code: &lt;a href=&quot;https://github.com/JLouback/nlp-CKY/blob/master/relabel_rare.py&quot;&gt;relabel_rare.py&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Usage: python relabel_rare.py [input_file]&lt;/p&gt;

&lt;p&gt;Pseudocode:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Uses Python Counter to obtain word counts in [input_file]; removes all word-count pairs with count &amp;lt; 5, store remaining pairs in a dictionary named rare_words.&lt;/li&gt;
  &lt;li&gt;Iterates through each line in [input file], checks if word is in rare words dictionary, if so, replaces word with &lt;em&gt;RARE&lt;/em&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Step 1. Get binary, unary and terminal rule counts&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Python code: &lt;a href=&quot;https://github.com/JLouback/nlp-CKY/blob/master/count_cfg_freq.py&quot;&gt;count_cfg_freq.py&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Pseudocode: Iterate through each line in the &lt;strong&gt;parse_train.dat&lt;/strong&gt; file and obtain counts for binary rules (example: S -&amp;gt; NP VP), unary rules (DET -&amp;gt; the) and nonterminals (examples: ADJ, VERB, etc). The output has one count+rule per line, each item of the line is separated by spaces. The line format is&lt;/p&gt;

&lt;p&gt;{count} NONTERMINAL {X} in the case of listing a nonterminal.&lt;/p&gt;

&lt;p&gt;{count} UNARY {X} {Y} in the case of a unary rule &lt;em&gt;X -&amp;gt; Y&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;{count} BINARY {X} {Y} {Z} in the case of a binary rule &lt;em&gt;X -&amp;gt; Y Z&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The abbreviations used are pretty intuitive. It’s not entirely necessary to understand the meaning of said abbreviations, but here’s a list of the most common nonterminals:&lt;/p&gt;

&lt;p&gt;S   start of sentence&lt;/p&gt;

&lt;p&gt;NP  nominal phrase&lt;/p&gt;

&lt;p&gt;VP  verbal phrase&lt;/p&gt;

&lt;p&gt;NOUN  noun&lt;/p&gt;

&lt;p&gt;VERB  verb&lt;/p&gt;

&lt;p&gt;ADJ   adjective&lt;/p&gt;

&lt;p&gt;ADV   adverb&lt;/p&gt;

&lt;p&gt;PP  preposition&lt;/p&gt;

&lt;p&gt;DET   determinant&lt;/p&gt;

&lt;p&gt;The script listed above (method get_counts) will return 3 dictionaries. The dictionary with terminal counts has a string as key and an int as value. The key is the terminal symbol, value is the count. For example, {“DET”:5623}. The unary counts have a string as key and a dictionary with {string:int} as value. The primary key is the unary symbol; the value has the actual word as key and it’s count. For example, {“DET”:{“the”:4833}}. Finally the binary counts are also a string as key and a dictionary with {string:int} as value. The primary key is still the X non-terminal in the binary rule X -&amp;gt; Y Z; the value has a dictionary with “Y Z” (the right had part of the binary rule) with the respective count as value. For example, {“S”:{“NP VP”:101788}}.&lt;/p&gt;

&lt;p&gt;It’s not as bad as it sounds.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2. Context-Free Grammar to Probabilistic Context-Free Grammar: calculate rule parameters&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Python code: &lt;a href=&quot;https://github.com/JLouback/nlp-CKY/blob/master/cyk.py&quot;&gt;cyk.py&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Get the 3 dictionaries with terminal, unary and binary rule counts.
The following steps are done for each line (sentence) in the development data. Basically this is where it all happens, after all you want to parse a sentence with POS tags. The amount of sentences is irrelevant; just useful to measure accuracy and performance.&lt;/li&gt;
  &lt;li&gt;Create two matrices that are nx(n+1) where n is the length of the sentence (number of words and punctuation symbols). Both matrices contain dictionaries. One will be used to store probabilities of a unary rule (example: {“DET”:0.28}) the other will contain the ‘backpointer’, rather the actual word the unary rule is being applied to (example: {“DET”:”the”}). These probabilities and backpointers will be stored on the diagonal starting at [0][1] of their respective matrices.&lt;/li&gt;
  &lt;li&gt;Here’s the part that gets slightly confusing to read the code. Starting from the upper left corner (i.e. coordinates, 0 and 1 if your language starts with 0 as the first position of an array), go adding binary rules to the diagonal of probabilities/backpointers filling up the upper right triangle of the matrix:
    &lt;ul&gt;
      &lt;li&gt;For all the binary rules in your list (dictionary of counts) check if the non-terminal beside the current position in the probability matrix (say at [0][1]) and the one “bellow” on the diagonal (in this case, [1][2]) are existing keys in the probability matrix. Note that if you are further above the diagonal starting at [0][1] built in step 2, you will have more options of combinations. For example, when filling in [0][3], you can check [0][1] and [1][2], [0][2] and [1][2], [0][2] and [1][3], [1][2] and [0][3].&lt;/li&gt;
      &lt;li&gt;If so, calculate the binary rule probability as
  (binary &lt;em&gt;X -&amp;gt; Y Z&lt;/em&gt; rule count / non-terminal count of &lt;em&gt;X&lt;/em&gt;) * (probability of unary rule for &lt;em&gt;Y&lt;/em&gt;) * (probability of unary rule for &lt;em&gt;Z&lt;/em&gt;)&lt;/li&gt;
      &lt;li&gt;If there hasn’t yet been a key-value pair stored in the probability matrix [0][2] with the key &lt;em&gt;X&lt;/em&gt;, set the value of key &lt;em&gt;X&lt;/em&gt; to the probability above. If not, check if the current binary rule has higher probability and if so, update the &lt;em&gt;X&lt;/em&gt; entry. Update the backpointer matrix at [0][2] to contain {“X”:”Y Z”} (if it’s a new key or if the current probability is the highest yet).&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;The upper right corner of the matrix should have an entry with the sentence starter symbol S. This dictionary would only have one entry because it would use the highest probability rule (it adds up). If it doesn’t (say your set doesn’t use starter symbols) it’s no issue, just ‘trace’ each value in the dictionary (recursively) to see which alternative has the highest probability.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;Return the parse tree with highest probability.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Step 3 is easier with examples. Take the sentence “The dog barked”. So in step 2 you’ll probably have entered the pair {“DET”:”the”} in the backpointer matrix at [0][1] and {“DET”:0.12} (random probability value used here) in the probability matrix at [0][1]. You’d also enter {“NOUN”:”dog”} and {“NOUN”:0.2} at positions [1][2] in each matrix. So take the rule &lt;em&gt;NP -&amp;gt; ADJECTIVE NOUN&lt;/em&gt;. Although &lt;em&gt;DET&lt;/em&gt; and &lt;em&gt;NOUN&lt;/em&gt; will likely have been matched to “the” and dog” and would have been included in the probability/backpointer matrix, the non-terminal &lt;em&gt;ADJECTIVE&lt;/em&gt; wouldn’t match to “the” so this binary rule is a no-go. Now for the rule &lt;em&gt;NP -&amp;gt; DET NOUN&lt;/em&gt; we have a match, so we add an entry {“NP”:0.23} to the probability matrix at [0][2] and {“NP”:”DET NOUN”} to position [0][2] in the backpointer matrix. We fill up the upper right triangle of the matrix.&lt;/p&gt;

&lt;p&gt;The great thing is, this algorithm can tolerate tagging errors. Let’s say once or a few times the word “the” was tagged as “ADJECTIVE”. So in the backpointer matrix [0][1] you would store the original entry {“DET”:”the”} but also {“ADJECTIVE”:”the”}. In the probability matrix at [0][1] you’d store {“DET”:q&lt;sub&gt;1&lt;/sub&gt;} and {“ADJECTIVE”:q&lt;sub&gt;2&lt;/sub&gt;} where q&lt;sub&gt;1&lt;/sub&gt; and q&lt;sub&gt;2&lt;/sub&gt; are the probabilities of the unary rules &lt;em&gt;DET -&amp;gt; the&lt;/em&gt; and &lt;em&gt;ADJECTIVE -&amp;gt; the&lt;/em&gt;. Now it’s very likely that q&lt;sub&gt;1&lt;/sub&gt; &amp;gt; q&lt;sub&gt;2&lt;/sub&gt; (unless you have a really badly tagged training set). When checking which binary rule that starts with &lt;em&gt;NP&lt;/em&gt; is best, you’ll probably run into &lt;em&gt;NP -&amp;gt; DET NOUN&lt;/em&gt; and &lt;em&gt;NP -&amp;gt; ADJECTIVE NOUN&lt;/em&gt;. These two binary rules could both be equally likely in their own right, let’s say they both equal b.&lt;/p&gt;

&lt;p&gt;p(NP -&amp;gt; DET NOUN) == p(NP -&amp;gt; ADJECTIVE NOUN) == b&lt;/p&gt;

&lt;p&gt;p(NOUN -&amp;gt; dog) == c&lt;/p&gt;

&lt;p&gt;p(DET -&amp;gt; the) == q1&lt;/p&gt;

&lt;p&gt;p(ADJECTIVE -&amp;gt; the) == q2&lt;/p&gt;

&lt;p&gt;and q1 &amp;gt; q2&lt;/p&gt;

&lt;p&gt;then b * q1 * c &amp;gt; b * q2 * c&lt;/p&gt;

&lt;p&gt;So you will chose the correct option &lt;em&gt;NP -&amp;gt; DET NOUN&lt;/em&gt; as the subtree root at [0][2].&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Evaluation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Prof. Michael Collins provided a &lt;a href=&quot;https://github.com/JLouback/nlp-CYK/blob/master/pretty_print_parse.py&quot;&gt;script to pretty print the parse trees&lt;/a&gt; and a &lt;a href=&quot;https://github.com/JLouback/nlp-CYK/blob/master/eval_parser.py&quot;&gt;performance evaluation script&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Usage: 
python pretty_print_parse.py [prediction_file]
python eval_parser.py parser_dev.key [prediction_file]&lt;/p&gt;

</description>
        <pubDate>Sat, 22 Oct 2016 10:06:52 +0000</pubDate>
        <link>http://julianalouback.com/tech/2016/10/22/nlp-pos-tagging-cyk/</link>
        <guid isPermaLink="true">http://julianalouback.com/tech/2016/10/22/nlp-pos-tagging-cyk/</guid>
        
        
        <category>tech</category>
        
      </item>
    
      <item>
        <title>NLP: Viterbi Named-Entity Tagger</title>
        <description>&lt;p&gt;During my MSc program, I was lucky to squeeze into &lt;a href=&quot;http://www.cs.columbia.edu/~mcollins/&quot;&gt;Michael Collin&lt;/a&gt;’s NLP class. We used his &lt;a href=&quot;https://www.coursera.org/course/nlangp&quot;&gt;Coursera course&lt;/a&gt; as part of the program, which I’d highly recommend.&lt;/p&gt;

&lt;p&gt;Recently I decided to review my NLP studies and I believe the best way to learn or relearn a subject is to teach it. This is one in a series of 4 posts with a walk-through of the algorithms we implemented during the course. I’ll provide links to my code hosted on Github.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclaimer: Before taking this NLP course, the only thing I knew about Python was that ‘it’s the one without curly brackets’. I learned Python on the go while implementing these algorithms. So if I did anything against Python code conventions or flat-out heinous, I apologize and thank you in advance for your understanding. Feel free to write and let me know.&lt;/em&gt;&lt;/p&gt;

&lt;h2 id=&quot;the-concept&quot;&gt;The Concept&lt;/h2&gt;

&lt;p&gt;To quote &lt;a href=&quot;https://en.wikipedia.org/wiki/Named-entity_recognition&quot;&gt;Wikipedia&lt;/a&gt;, “Named-entity recognition (I’ve always known it as tagging) is a subtask of information extraction that seeks to locate and classify elements in text into predefined categories such as the names of persons, organizations, locations, expressions of times, quanitites, monetary values, percentages, etc.”&lt;/p&gt;

&lt;p&gt;For example, the algorithm receives as input some text&lt;/p&gt;

&lt;p&gt;“Bill Gates founded Microsoft in 1975.”&lt;/p&gt;

&lt;p&gt;and outputs&lt;/p&gt;

&lt;p&gt;“Bill Gates[person] founded Microsoft[organization] in 1975[date].”&lt;/p&gt;

&lt;p&gt;Off the top of my head, some useful applications are document matching (ex. a document containing Gates[person] may not be on the same topic as one containing gates[object]) and query searches. I’m sure there are lots more, if you check out Collin’s Coursera course he may discuss this in greater depth.&lt;/p&gt;

&lt;h2 id=&quot;the-requirements&quot;&gt;The Requirements&lt;/h2&gt;

&lt;p&gt;Development data: The file &lt;a href=&quot;https://github.com/JLouback/nlp-viterbi/blob/master/ner_dev.dat&quot;&gt;ner_dev.dat&lt;/a&gt; provided by prof. Michael Collins has a series of sentences separated by an empty line, one word per line.&lt;/p&gt;

&lt;p&gt;Training data: The file &lt;a href=&quot;https://github.com/JLouback/nlp-viterbi/blob/master/ner_train.dat&quot;&gt;ner_train.dat&lt;/a&gt; provided by prof. Michael Collins has a series of sentences separated by an empty line, one word and tag per line, speparated by a space.&lt;/p&gt;

&lt;p&gt;Word-tag count data: The file &lt;a href=&quot;https://github.com/JLouback/nlp-viterbi/blob/master/ner.counts&quot;&gt;ner.counts&lt;/a&gt; has the format [count] [type of tag] [label] [word]. The tags used are &lt;em&gt;RARE&lt;/em&gt;, O, I-MISC, I-PER, I-ORG, I-LOC, B-MISC, B-PER, B-ORG, B-LOC. The tag O means it’s not an NE. This file is generated by &lt;a href=&quot;https://github.com/JLouback/nlp-viterbi/blob/master/count_freqs.py&quot;&gt;count_freqs.py&lt;/a&gt;, a script provided by prof. Michael Collins. Run count_freqs.py on the training data ner_train.dat&lt;/p&gt;

&lt;h2 id=&quot;the-algorithm&quot;&gt;The Algorithm&lt;/h2&gt;
&lt;p&gt;Python code: &lt;a href=&quot;https://github.com/JLouback/nlp-viterbi/blob/master/viterbi.py&quot;&gt;viterbi.py&lt;/a&gt;
Usage: python viterbi.py ner.counts ngram.counts [input_file] &amp;gt; [output_file]
Summary: The Viterbi algorithm finds the maximum probability path for a series of observations, based on emission and transition probabilities. In a Markov Process, emission is the probability of an output given a state and transition is the probability of transitioning to the state given the previous states. In our case, the emission parameter e(x|y) = the probability of the word being x given you attributed tag y. If your training data had 100 counts of ‘person’ tags, one of which is the word ‘London’ (I know a guy who named his kid London), e(‘London’|’person’) = 0.01. Now with 50 counts of ‘location’ tags, 5 of which are ‘London’, e(‘London’|’location’) = 0.1 which clearly trumps 0.01. The transition parameter q(y&lt;sub&gt;i&lt;/sub&gt; | y&lt;sub&gt;i-1&lt;/sub&gt;, y&lt;sub&gt;i-2&lt;/sub&gt;) = the probability of putting tag y in position i given it’s two previous tags. This is calculated by Count(trigram)/Count(bigram). For each word in the development data, he Viterbi algorithm will associate a score for a word-tag combo based on the emission and transition parameters it obtained from the training data. It does this for every possible tag and sees which is more likely. Clearly this won’t be 100% correct as natural language is unpredictable, but you should get pretty high accuracy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Optional Preprocessing&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Re-label words in training data with frequency &amp;lt; 5 as ‘&lt;em&gt;RARE&lt;/em&gt;’ - This isn’t required, but useful. Re-run count_freqs.py if used.&lt;/p&gt;

&lt;p&gt;Python code: &lt;a href=&quot;https://github.com/JLouback/nlp-viterbi/blob/master/label_rare.py&quot;&gt;label_rare.py&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Usage: python label_rare.py [input_file]&lt;/p&gt;

&lt;p&gt;Pseudocode:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Uses Python Counter to obtain word counts in [input_file]; removes all word-count pairs with count &amp;lt; 5, store remaining pairs in a dictionary named rare_words.&lt;/li&gt;
  &lt;li&gt;Iterates through each line in [input file], checks if word is in rare words dictionary, if so, replaces word with &lt;em&gt;RARE&lt;/em&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Step 1. Get Count(y) and Count(x~y)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Python code: &lt;a href=&quot;https://github.com/JLouback/nlp-viterbi/blob/master/emission_counts.py&quot;&gt;emission_counts.py&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Pseudocode:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Iterate through each line in ner.counts file and store each word-label-count combo in a dictionary count_xy and update the dictionary of count_y. For example count_xy[Peter][I-PER] returns the number of times the word ‘Peter’ was labeled ‘I-PER’ in the training data and count_y[I-PER] the total number of ‘I-PER’ tags. The dictionary count_y contains 8 items, one for each label ( RARE , O, I-MISC, I-PER, I-ORG, I-LOC, B-MISC, B-PER, B-ORG, B-LOC);&lt;/li&gt;
  &lt;li&gt;Return count_xy, count_y&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Step 2. Get bigram and trigram counts&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Python code: &lt;a href=&quot;https://github.com/JLouback/nlp-viterbi/blob/master/&quot;&gt;transition_counts.py&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Pseudocode:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Iterate through each line in the n-gram_counts file&lt;/li&gt;
  &lt;li&gt;If the line contains ’2-GRAM’ add an item to the bigram_counts dictionary using the bigram (two space-separated labels following the tag type ‘2-gram’) as key, count as value. This dictionary will contain Count(y&lt;sub&gt;i-2&lt;/sub&gt;,y&lt;sub&gt;i-1&lt;/sub&gt;).&lt;/li&gt;
  &lt;li&gt;If the line contains ’3-GRAM’, add an item to the trigram_counts dictionary using the trigram as key, count as value. This dictionary will contain Count(y&lt;sub&gt;i-2&lt;/sub&gt;, y&lt;sub&gt;i-1&lt;/sub&gt;, y&lt;sub&gt;i&lt;/sub&gt;).&lt;/li&gt;
  &lt;li&gt;Return dictionaries of bigram and trigram counts.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Step 3. Viterbi&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;(For each line in the [input_file]):&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;If the word was seen in training data (present in the count_xy dictionary), for each of the possible labels for the word:&lt;/li&gt;
  &lt;li&gt;Calculate emission = count_xy[word][label] / float(count_y[label]&lt;/li&gt;
  &lt;li&gt;Calculate transition = trigram_counts[trigram])/float(bigram_counts[bigram] Note: y&lt;sub&gt;i-2&lt;/sub&gt; = *, y&lt;sub&gt;i-1&lt;/sub&gt; = * for the first round&lt;/li&gt;
  &lt;li&gt;Set probability = emission x transition&lt;/li&gt;
  &lt;li&gt;Update max(probability) and arg max if needed.
2 If the word was not seen in the training data:&lt;/li&gt;
  &lt;li&gt;Calculate emission = count xy[&lt;em&gt;RARE&lt;/em&gt;][label] / float(count y[label].&lt;/li&gt;
  &lt;li&gt;
    &lt;table&gt;
      &lt;tbody&gt;
        &lt;tr&gt;
          &lt;td&gt;Calculate q(y&lt;sub&gt;i&lt;/sub&gt;&lt;/td&gt;
          &lt;td&gt;y&lt;sub&gt;i-1&lt;/sub&gt;, y&lt;sub&gt;i-2&lt;/sub&gt;) = trigram counts[trigram])/float(bigram counts[bigram]. Note: y&lt;sub&gt;i-2&lt;/sub&gt; = ∗, y&lt;sub&gt;i-1&lt;/sub&gt; = ∗ for the first round&lt;/td&gt;
        &lt;/tr&gt;
      &lt;/tbody&gt;
    &lt;/table&gt;
  &lt;/li&gt;
  &lt;li&gt;Set probability = emission × transition&lt;/li&gt;
  &lt;li&gt;Update max(probability) if needed, arg max = &lt;em&gt;RARE&lt;/em&gt;&lt;/li&gt;
  &lt;li&gt;Write arg max and log(max(probability)) to output file.&lt;/li&gt;
  &lt;li&gt;Update y&lt;sub&gt;i-2&lt;/sub&gt;, y&lt;sub&gt;i-1&lt;/sub&gt;.&lt;/li&gt;
  &lt;li&gt;Update y&lt;sub&gt;i-2&lt;/sub&gt;, y&lt;sub&gt;i-1&lt;/sub&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Evaluation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Prof. Michael Collins provided an evaluation script &lt;a href=&quot;https://github.com/JLouback/nlp-viterbi/blob/master/eval_ne_tagger.py&quot;&gt;eval_ne_tagger.py&lt;/a&gt; to verify the output of your Viterbi implementation.
Usage: python eval_ne_tagger.py ner_dev.key [output_file]&lt;/p&gt;

</description>
        <pubDate>Sun, 10 Jan 2016 10:06:52 +0000</pubDate>
        <link>http://julianalouback.com/tech/2016/01/10/nlp-viterbi-named-entity-tagger/</link>
        <guid isPermaLink="true">http://julianalouback.com/tech/2016/01/10/nlp-viterbi-named-entity-tagger/</guid>
        
        
        <category>tech</category>
        
      </item>
    
      <item>
        <title>PaperTrail - Powered by IBM Watson</title>
        <description>&lt;p&gt;On the final semester of my MSc program at &lt;a href=&quot;http://engineering.columbia.edu/&quot;&gt;Columbia SEAS&lt;/a&gt;, I was lucky enough to be able to attend a seminar course taught by &lt;a href=&quot;http://researcher.watson.ibm.com/researcher/view.php?person=us-gliozzo&quot;&gt;Alfio Gliozzo&lt;/a&gt; entitled Q&amp;amp;A with IBM Watson. A significant part of the course is dedicated to learning how to leverage the services and resources available on the &lt;a href=&quot;https://www.ibm.com/smarterplanet/us/en/ibmwatson/developercloud/&quot;&gt;Watson Developer Cloud&lt;/a&gt;. This post describes the course project my team developed, the PaperTrail application.&lt;/p&gt;

&lt;h2 id=&quot;project-proposal&quot;&gt;Project Proposal&lt;/h2&gt;

&lt;p&gt;Create an application to assist in the development of future academic papers. Based on a paper’s initial proposal, Paper Trail predicts publications to be used as references or acknowledgement of prior art and provides a trend analysis of major topics and methods.&lt;/p&gt;

&lt;p&gt;The objective is to speed the discovery of relevant papers early in the research process, and allow for early assessment of the depth of prior research concerning the initial proposal.&lt;/p&gt;

&lt;h2 id=&quot;meet-the-team&quot;&gt;Meet the Team&lt;/h2&gt;

&lt;p&gt;Wesley Bruning, Software Engineer, MSc. in Computer Science&lt;/p&gt;

&lt;p&gt;Xavier Gonzalez, Industrial Engineer, MSc. in Data Science&lt;/p&gt;

&lt;p&gt;Juliana Louback, Software Engineer, MSc. in Computer Science&lt;/p&gt;

&lt;p&gt;Aaron Zakem, Patent Attorney, MSc. in Computer Science&lt;/p&gt;

&lt;h2 id=&quot;prior-art&quot;&gt;Prior Art&lt;/h2&gt;

&lt;p&gt;A significant amount of attention has been given to this topic over the past few decades. The table below shows the work the team deemed most relevant due to recency, accuracy and similarity of functionality.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/priorArt.jpg&quot; alt=&quot;priorArt&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The variation in accuracy displayed is a result of experimentation with different dataset sizes and algorithm variations. More information and details can be found in the &lt;a href=&quot;/assets/prior_art_report.pdf&quot;&gt;prior art report&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The main differential of PaperTrail is providing a form of access to the citation prediciton and trend analysis algorithm. With the exception of the project by McNee et al., these algorithmns aren’t currently available for general use. The application on researchindex.net is open to use but its objective is to rank publications and authors for given topics.&lt;/p&gt;

&lt;h2 id=&quot;algorithm&quot;&gt;Algorithm&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Citation Prediction:&lt;/strong&gt; PaperTrail builds on the work done by Wolski’s team in Fall 2014. This algorithmn builds a reference graph used to define research communities, with an associated vector of topic scores generated by an LDA model. The papers in each research community are then ranked by importance within the community with a custom ranking algorithm. When a target document is given to algorithm as input, the LDA model is used to generate a vector of topics that are present in the document. The communities with the most similar topic vectors are selected and the publications within these communities with highest rank and greatest similarity to the input document are recommended as references. A more detailed description can be found &lt;a href=&quot;https://edblogs.columbia.edu/comse6998-006-2014-1/category/projects/project4/&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Trend Analysis:&lt;/strong&gt; Initially, the idea was to use the &lt;a href=&quot;http://www.alchemyapi.com/products/alchemydata-news&quot;&gt;AlchemyData News API&lt;/a&gt; to obtain statistics pertaining to the amount of publications on a given topic over time. However, with the exception of buzz-words (i.e. ‘big data’), many more specialized topics appeared very infrequently in news articles, if at all. This isn’t entirely surprising given the target audience of PaperTrail. As a work around, we use the &lt;a href=&quot;http://www.alchemyapi.com/products/alchemylanguage/keyword-extraction&quot;&gt;Alchemy Language API&lt;/a&gt; to extract keywords from the abstracts in the dataset, in addition to relevance scores. The PaperTrail database could then be queried for entry counts for a given year and keyword to provide an indication of publication trends in academia. Note that the &lt;a href=&quot;http://www.alchemyapi.com/products/alchemylanguage/keyword-extraction&quot;&gt;Alchemy Language API&lt;/a&gt; extracts multiple-word ‘keywords’ as well as single words.&lt;/p&gt;

&lt;h2 id=&quot;data&quot;&gt;Data&lt;/h2&gt;

&lt;p&gt;To maintain consistency with Wolski’s project, we are using the &lt;a href=&quot;http://dblp.uni-trier.de/&quot;&gt;DBLP&lt;/a&gt; data as made available on &lt;a href=&quot;https://aminer.org/billboard/citation&quot;&gt;aminer.org&lt;/a&gt;. The DBLP-Citation-network V5 dataset contains 1,572,277 entries; we are limited to the use of entries that contain both abstracts and citations, bringing the dataset size down to 265,865 entries.&lt;/p&gt;

&lt;h2 id=&quot;architecture&quot;&gt;Architecture&lt;/h2&gt;

&lt;p&gt;A high-level visualization of the project architecture is displayed below. Before launching PaperTrail, it’s necessary to train Wolski’s algorithm offline. Currently any documentation with regard to the performance of said algorithm is unavailable; the PaperTrail project will include an evaluation phase and report the findings made.&lt;/p&gt;

&lt;p&gt;The PaperTrail app and database will be hosted on the &lt;a href=&quot;http://www.ibm.com/cloud-computing/bluemix/?cm_mmc=search-gsn-_-branded-Bluemix-general-_-bluemix-_-usa-bm-mkt-oww&quot;&gt;Bluemix Platform&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/ptArchitecture.png&quot; alt=&quot;ptArchitecture&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;status-report&quot;&gt;Status Report&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Phases completed:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;Project design&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Prior art research&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Data cleansing&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Development and deployment of an alpha version of the PaperTrail app&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Phases under development:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;Algorithm training and evaluation&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Keyword extraction&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;MapReduce of publication frequency by year and topic&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Data visualization component&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

</description>
        <pubDate>Sat, 14 Nov 2015 10:06:52 +0000</pubDate>
        <link>http://julianalouback.com/tech/2015/11/14/papertrail-powered-by-ibm-watson/</link>
        <guid isPermaLink="true">http://julianalouback.com/tech/2015/11/14/papertrail-powered-by-ibm-watson/</guid>
        
        
        <category>tech</category>
        
      </item>
    
      <item>
        <title>Getting into Google - The Timeline</title>
        <description>&lt;p&gt;As part of my &lt;a href=&quot;http://www.iie.org/Programs/Brazil-Scientific-Mobility&quot;&gt;Brazil Scientific Mobility Masters Cohort Program&lt;/a&gt; (what a mouthful), I have to engage in research or an internship related to my field of study during the summer - no 3 month vacation. This isn’t as terrible as it sounds, particularly if you consider my summer will be spent working at Google.&lt;/p&gt;

&lt;p&gt;You don’t get that much desired offer letter before going through quite a battery of “entrance exams”. The selection process isn’t easy - or at least I didn’t think so. I thought I’d write about somthing I concerned myself much about: the admission timeline. Hopefully this can be of help to people currently going through the process or for those who’d like to know what they’re getting into.&lt;/p&gt;

&lt;h2 id=&quot;the-timeline&quot;&gt;The Timeline&lt;/h2&gt;

&lt;p&gt;You’ll hear about this in most blogs. The intern selection process is usually divided into three parts: phone interview, technical interview(s), matching. From what I researched, my process took longer than usual. This may be because it happened over the holidays/end of year. This was nerve-wracking for me, so I thought I’d share my case so others worry less. I used my email history to get dates, so this should be pretty accurate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;0. The First Application:&lt;/strong&gt; As I was looking up old emails, I found an email dated ~ Feb 2013, the end of my junior year in undergrad, kindly informing me that I was not chosen for an internship at Google. I’d forgotten I’d applied way back when. But they said they’d keep my resume on file and turns out they actually did. One year later in June 2014, I get an email from a recruiter. She wanted to interview me for a full time position. My resume included my June 2014 expected graduation so I’m guessing that’s why I got a callback.&lt;/p&gt;

&lt;p&gt;By that time I had already accepted a grant funding my MS at Columbia, so I explained I would only be free for a summer 2015 internship. My recruiter was great about it, said she’d recommend me to the U.S. internship recruiting department, but suggested we go ahead and have a phone interview. Lesson here, getting a ‘no’ doesn’t mean you’re no good. It could be the wrong time, or maybe you just need to work a bit more, learn a bit more and try again.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Phone Interview - June 2014:&lt;/strong&gt; The phone interview is no biggie. A few tech-related questions, but all the kind you can answer in one sentence. Mostly to get to know you, your experience, interests, etc. ~30 min.&lt;/p&gt;

&lt;p&gt;After the call, I wrote thanking my recruiter for the opportunity. She wrote back that day giving me prep material for the technical interview, which I assume meant I passed that part. As I was only applying for summer 2015, she informed me she’d be in touch after the summer which is when the technical interviews would take place.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. The Second Application - September 2014:&lt;/strong&gt; In mid-September, the Google recruiter contacted me again, asking for an updated transcript, resume, the works. I think I applied around then to a summer 2015 software engineering internship. I don’t remember if I was instructed to, or if I simply did it on a hunch.&lt;/p&gt;

&lt;p&gt;By the end of the month (26th to be exact), I got an email from another recruiter scheduling the technical interview. The interviews were to take place about a month later. The recruiter gave me a window of time to have the interviews, I gave them several days I’d be free and he chose one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. The Technical Interviews - October 2014:&lt;/strong&gt; Both my technical interviews took place on the afternoon October 22nd. I got a very sweet, “you can do it” email from my recruiter with tips and helpful suggestions. I should take the time to mention how incredibly nice all the recruiters I interacted with were. They really only get the best people to work at Google. It was an A+ experience.&lt;/p&gt;

&lt;p&gt;The interviews were held over a video call, each 1 hour long, all coding done on Google Docs. This done, I patiently waited, or at least I tried very hard to be patient. I wasn’t. Not my forté. I refreshed my inbox every 5-10 minutes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. The Technical Interview Turnout - November 2014:&lt;/strong&gt; Near the date of the technical interviews, I was informed I would get an answer in 1-2 weeks. I interviewed on a Wednesday, October 22nd; I got the call back on a Monday, November 3rd. Almost exactly 2 weeks. Felt like a year. If they’d waited another day I think I would’ve developed an ulcer I was so anxious.&lt;/p&gt;

&lt;p&gt;It was great to hear I passed the technical interviews, but as they say, it’s not over until the fat lady sings. The metaphorical ‘fat lady’ was not singing. Once you pass the technical interviews, you go into a matching phase. You fill out a candidate profile with your skills, location preferences, strengths and interests, then the teams needing an intern contact your recruiter should you seem like a good fit.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. The Matching Phase - November 2014 to January 2015:&lt;/strong&gt; I was told that if you don’t get matched in circa 6 weeks, you bow out of the intern selection process. This isn’t an urban legend, one of my fellow students did make it to the matching phase but didn’t get matched (he did land a killer offer later on though). He’s an excellent engineer, but there is such a thing as wrong place and/or time. Rumor has it he received a full-time job offer from Google upon graduation.&lt;/p&gt;

&lt;p&gt;I learned I passed the technical interview part in the first week of November and on the second week of December I received news of a possible team match. That was more or less 6 weeks, yikes. We scheduled a call between me and a member of the team. This isn’t a technical interview, just a ‘getting to know you’ talk, to see if you’re a good fit. I think a day or two later I heard back from the team, they wanted me! The team seemed great, I got to meet them when I flew out to the West Coast, but the project itself was not in a domain I was particularly experienced in. I asked my recruiter if I could wait a bit before making a decision. Bold move, Cotton.&lt;/p&gt;

&lt;p&gt;Apparently the 6 week deadline isn’t necessarily a hard and fast rule. About three weeks later in the beginning of January I heard about another possible match. This time the project was perfectly aligned to both my strengths and my interests. I could not hide how interested I was in working on the project. The kind of thing I’d do for free, heck, I’d pay them to let me work on the project. A couple days later, I heard back from the team lead confirming their interest in working with me.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. The Offer Letter - January 2015:&lt;/strong&gt; On January 17th, 2015, I got that awesome offer letter, then spent the whole day smiling like an idiot. Actually that went on much longer than that, now and then I remember I’m heading there soon (in two weeks) and I smile stupidly again.&lt;/p&gt;

&lt;p&gt;I had two weeks to accept/rejct the offer, needless to say I signed it the same day. So that was that. I think usually the phone interview is usually right before the technical interviews. Even so, my entire hiring process took about 3 months. If this happens to you, don’t take it as a bad sign. I hear good things come to those who wait.&lt;/p&gt;

</description>
        <pubDate>Mon, 05 Jan 2015 10:06:52 +0000</pubDate>
        <link>http://julianalouback.com/tech/2015/01/05/getting-into-google/</link>
        <guid isPermaLink="true">http://julianalouback.com/tech/2015/01/05/getting-into-google/</guid>
        
        
        <category>tech</category>
        
      </item>
    
      <item>
        <title>JSCommunicator at xTupleCon 2014</title>
        <description>&lt;p&gt;Two weeks ago I left NYC for a day trip to Norfolk, Virginia, to attend &lt;a href=&quot;http://xtuple.com/xtuple-conference-2014&quot;&gt;xTupleCon 2014&lt;/a&gt;. For those who don’t know, &lt;a href=&quot;http://xtuple.com&quot;&gt;xTuple&lt;/a&gt; is an incredibly reknown open source Enterprise Resource Planning (ERP) software. If you’ve been following this blog, you might recall that during my participation in the Google Summer of Code 2014, I wrote a beta JSCommunicator extension for xTuple (to see how I went about doing that, look up &lt;a href=&quot;/tech/2014/07/25/kickstarting-the-jscommunicator-xtuple-extension/&quot;&gt;Kickstarting the JSCommunicator-xTuple extension&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Now, I wasn’t flying down to Virginia on the eve of my first grad school midterms (gasp!) for fun - although I will admit, I enjoyed myself a fair amount. My GSoC mentor, &lt;a href=&quot;http://danielpocock.com&quot;&gt;Daniel Pocock&lt;/a&gt; was giving a talk about WebRTC and JSCommunicator at xTupleCon and invited me to participate. So that was the first perk of going to xTupleCon, I got to finally meet my GSoC mentor in person!&lt;/p&gt;

&lt;p&gt;During the presentation, Daniel provided a high level expanation of WebRTC and how it works. WebRTC (Web Real Time Communications) enables real time transmission of audio, video and data streams through browser-to-browser applications. This is one of the many perks of WebRTC; it doesn’t require installation of plugins, making its use less vulnerable to security breaches. Websockets are used for the signalling channel and the SIP or XMPP can be used as the signaling protocol - in JSCommunicator, we used SIP. What was also done in JSCommunicator and can be done in other applications is to use a library to implement the signaling and SIP stack. JSCommunicator uses (and I highly reccomend) &lt;a href=&quot;http://jssip.com&quot;&gt;JsSIP&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The basic SIP architecture is comprised of peers and a SIP Proxy Server that supports SIP over Websockets transport:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/sipArch.png&quot; alt=&quot;sipArch&quot; /&gt;&lt;/p&gt;

&lt;p&gt;If you have users behind NAT, you’ll also need a TURN server for relay:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/sipArch2.png&quot; alt=&quot;sipArch2&quot; /&gt;&lt;/p&gt;

&lt;p&gt;In both cases, setup is not too difficult, particularly if using &lt;a href=&quot;http://www.resiprocate.org&quot;&gt;reSIProcate&lt;/a&gt; which offers both the SIP proxy and the TURN server. Daniel Pocock has an excellent post on &lt;a href=&quot;http://danielpocock.com/get-webrtc-going-faster&quot;&gt;how to setup and configure your SIP proxy and TURN server&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;With regard to JSCommunicator, it is a generic telephone in HTML5 which can easily be embedded in any web site or web app. Almost every aspect of JSCommunicator is easily customizable. More about JSCommunicator setup and architecture is detailed in a &lt;a href=&quot;% post_url 2014-08-11-&quot;&gt;previous post&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The JSCommunicator-xTuple extension can be installed in xTuple as an npm package (xtuple-jscommunicator). It is still at a very beta - or even pre-beta - stage and there are various limitations; the configuration must be hard-coded and dialing is done manually as opposed to clicking on a contact. Some of these ‘limitations’ are features are on the wish list for future work. For example, some ideas for the next version of the extension are a click to dial from CRM records functionality and to bring up CRM records for an incomming call. Additionaly, the SIP proxy could be automatically installed with the xTuple server installation if desired.&lt;/p&gt;

&lt;p&gt;We closed the presentation with a live demo during which I made a video call from a JSCommunicator instance embedded in &lt;a href=&quot;http://freephonebox.net&quot;&gt;freephonebox.net&lt;/a&gt; to the JSCommunicator xTuple extension running on Daniel’s laptop. Despite the occasionally iffy hotel Wifi, the demo was a hit - at one point I even left the conference room and took a quick walk around the hotel and invited other xTupleCon attendees to say hello to those in the room. The audience’s reception was more enthusiastic than I anticipated, giving way to a pretty extensive Q&amp;amp;A session. It’s great to see more and more people interested in WebRTC, I can’t emphasize enough what a useful and versatile tool it is.&lt;/p&gt;

&lt;p&gt;Here’s an ‘action shot’ of part of the xTuple WebRTC presentation:
&lt;img src=&quot;/assets/xtuple.png&quot; alt=&quot;xtuple&quot; /&gt;&lt;/p&gt;

</description>
        <pubDate>Thu, 30 Oct 2014 10:06:52 +0000</pubDate>
        <link>http://julianalouback.com/tech/2014/10/30/jscommunicator-at-xtuplecon-2014/</link>
        <guid isPermaLink="true">http://julianalouback.com/tech/2014/10/30/jscommunicator-at-xtuplecon-2014/</guid>
        
        
        <category>tech</category>
        
      </item>
    
      <item>
        <title>Debconf 2014 and How I Became a Debian Contributor</title>
        <description>&lt;p&gt;&lt;strong&gt;Part 1 - Debconf 2014&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This year I went to my first &lt;a href=&quot;http://debconf14.debconf.org/&quot;&gt;Debconf&lt;/a&gt;, which took place in Portland, OR during the last week of August 2014. All in all I have to rate my experience as very enlightening and in the end quite fun.&lt;/p&gt;

&lt;p&gt;First of all, it was a little daunting to go to a conference in 1 - A city I’d never been to before; 2 - A conference with 300+ people, only 3 of which I knew and even then I only knew them virtually. Not to mention I was in the presence of some extremely brilliant and known contirbutors in the Debian community which was somewhat intimidating. Just to give you an idea, &lt;a href=&quot;http://en.wikipedia.org/wiki/Linus_Torvalds&quot;&gt;Linus Torvalds&lt;/a&gt; showed up for a Q&amp;amp;A session last Friday morning! Jealous? Actually I missed that too. It was kind of a last minute thing, booked for coincidentally the exact time I’d be flying out of Portland. I found out about it much too late. But luckily for me and maybe you, the session was filmed and can be seen &lt;a href=&quot;http://meetings-archive.debian.net/pub/debian-meetings/2014/debconf14/webm/QA_with_Linus_Torvalds.webm&quot;&gt;here&lt;/a&gt;. Isn’t that a treat?&lt;/p&gt;

&lt;p&gt;Point made, there are lots of really talented people there, both techies and non-techies. It’s easy to feel you’re out of league, at least I did. But I’d highly encourage you to ignore such feelings if you’re ever in the same situation. Debian has been built on for a long time now, but although a lot has been done, a lot still needs to be done. The Debian community is very welcoming of new contributors and users, regardless of the level of expertise. So far I haven’t been snubbed by anyone. To the contrary, all my interactions with the Debian community members has been extremely positive.&lt;/p&gt;

&lt;p&gt;So go ahead and attend the meetings and presentations, even if you think it’s not your area of expertise. Debconf was organized (or at least this one was) as a series of talks, meet ups and ad hoc sessions, some of which occured simultaneously. The sessions were all about different components of the Debian universe, from presenting new features to overviews of accomplishments to discussing issues and how to fix them. A schedule with the location and description of each session was posted on the &lt;a href=&quot;https://summit.debconf.org/debconf14/&quot;&gt;Debconf wiki&lt;/a&gt;. Sometimes none of the sessions at a certain time was on a topic I knew very much about. But I’d sit in anyways. There’s no rule to attending the sessions, no ‘minimum qualifications’ required. You’ll likely learn something new and you just might find out there is something you can do to contribute. There are also hackathons that are quite the thing or so I heard. Or you could walk about and meet new people, do some networking.&lt;/p&gt;

&lt;p&gt;I have to say networking was the highlight of the Debconf for me. Remember I said I knew about 3 people who were at the conference? Well, I had actually just corresponded with those people. I didn’t really &lt;em&gt;know&lt;/em&gt; them. So on my first day I spent quite some time shyly peeking at people’s name tags, trying to recognize someone I had ‘met’ over email or IRC. But with 300 or so people at the conference, I was unsuccessful. So I finally gave up on that strategy and walked up to a random person, stuck out my hand and said, “Hi. My name is Juliana. This is my first Debconf. What’s your name and what do you do for Debian?” This may not be according to protocol, but it worked for me. I got to meet lots of people that way, met some Debian contributos from my home country (Brazil), some from my current city (NYC), and yet others that had similar interests as I do who I might work with in the near future. For example, I love Machine Learning, I’m currently beginning my graduate studies on that track. Several Debian contributors offered to introduce me to a well known Machine Learning researcher and Debian contributor who is in NYC. Others had tried out &lt;a href=&quot;https://github.com/opentelecoms-org/jscommunicator&quot;&gt;JSCommunicator&lt;/a&gt; and had lots of suggestions for new features and fixes, or wanted to know more about the project and WebRTC in general. Also, not everyone there is a super experienced Debian contributor or user. There are a lot of newbies like me.&lt;/p&gt;

&lt;p&gt;I got to do a quick 20-min presentation and demo of the work I had done on JSCommunicator during GSoC 2014. Oh my goodness that was nerve-wracking, but not half as painful as I expected. My mentor (&lt;a href=&quot;http://danielpocock.com/&quot;&gt;Daniel Pocock&lt;/a&gt;) wisely suggested that when confronted with a question I didn’t know how to answer, to redirect the question to to the audience. Chances are, there is someone there that knows the answer. If not, it will at least spark a good discussion.&lt;/p&gt;

&lt;p&gt;When meeting new people at Debian, a question almost everyone asked is “How did you start working with/for Debian?”. So I thought it would be a good topic to post about.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Part 2 - How I Became a Debian Contributor&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Sometime in late October of 2013 (I think) I received an email from one of my professors at &lt;a href=&quot;http://www2.uniriotec.br/UNIRIO-CCET/&quot;&gt;UNIRIO&lt;/a&gt; forwarding a description of the &lt;a href=&quot;http://gnome.org/opw/&quot;&gt;Outreach Program for Women&lt;/a&gt;. OPW is a program organized by the &lt;a href=&quot;http://www.gnome.org/&quot;&gt;GNOME&lt;/a&gt; which endeavors to get more women involved in FOSS. OPW is similar to &lt;a href=&quot;https://developers.google.com/open-source/soc/?csw=1&quot;&gt;Google Summer of Code&lt;/a&gt;; you work remotely from home, guided by an assigned mentor. Debian was one of the 8 participating organizations that year. There was a list of project proposals which I perused, a few of them caught my eye and these projects were all Debian. I’d already been a fan of FOSS before. I had used the Ubuntu and Debian OS, I’d migrated to &lt;a href=&quot;http://www.gimp.org/&quot;&gt;GIMP&lt;/a&gt; from Photoshop and to &lt;a href=&quot;https://www.openoffice.org/&quot;&gt;Open Office&lt;/a&gt; from Microsoft Office, for example. I’d strongly advocated the use of some of my prefered open source apps and programs to my friends and family. But I hadn’t ever contributed to a FOSS project.&lt;/p&gt;

&lt;p&gt;There’s no time like the present, so I reached out the the mentor responsible for one of the projects I was interested in, &lt;a href=&quot;http://danielpocock.com&quot;&gt;Daniel Pocock&lt;/a&gt;. Daniel guided me through making a small contribution to a FOSS project, which serves as a token demonstration of my abilities and is part of the application process. I added a small feature to &lt;a href=&quot;https://github.com/ganglia/jmxetric&quot;&gt;JMXetric&lt;/a&gt; and suggested a fix for an &lt;a href=&quot;https://github.com/xtuple/qt-client/commit/7892bbe30bf345b7f7d4de24afed3b342c7751b0&quot;&gt;issue&lt;/a&gt; in the &lt;a href=&quot;http://www.xtuple.com&quot;&gt;xTuple&lt;/a&gt; project. Actually, I had forgotten about this. Recently I made another &lt;a href=&quot;/tech/2014/07/25/kickstarting-the-jscommunicator-xtuple-extension/&quot;&gt;contribution to xTuple&lt;/a&gt;, it’s funny to see things come full circle. I also had to write a profile-ish description of my experience and how I intended on contributing during OPW on the Debian wiki, if you’d like you can check it out &lt;a href=&quot;https://wiki.debian.org/Juliana%20Louback&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I wouldn’t follow my example to a T, because in the end I didn’t make the OPW selection. Actually, I take that back. The fact I wasn’t chosen for OPW that year doesn’t mean I was incompetent or incapable of making a valuable contribution. OPW and GSoC do not have unlimited resources; they can’t include everyone they’d like to. They receive thousands of proposals from very talented engineers and not everyone can participate at a given moment. But even though I wasn’t selected, like I said, I could still pitch in. It’s good to keep in mind that people usually aren’t paid to contribute to FOSS. It’s usually volunteer based, which I think is one of the beauties of the FOSS community and in my opinion one of the causes of it’s success and great quality. People contribute because they &lt;em&gt;want&lt;/em&gt; to, not because they &lt;em&gt;have&lt;/em&gt; to.&lt;/p&gt;

&lt;p&gt;I will say I was a little disappointed at not being chosen. But after being reassured that this ‘rejection’ wasn’t due to any lack on my part, I decided to continue contributing to the Debian project I’d applied to. I was begining the final semester of my undergraduate studies which included writing a thesis. To be able to focus on my thesis and graduate on time, I’d stopped working temporarily and was studying full time. But I didn’t want to lose practice and contributing to a FOSS project is a great way to stay in coding shape while doing something &lt;em&gt;useful&lt;/em&gt;. So continue contributing I did.&lt;/p&gt;

&lt;p&gt;It paid off. I gained experience, added value to a FOSS project and I think my previous contributions added weight to the application I later made for GSoC 2014. I passed this time. To be honest, I really wasn’t counting on it. Actually, I was certain I wouldn’t pass for some reason - insecure much? But with GSoC I wasn’t too anxious about it as I was with the OPW application because by then, I was already ‘hooked’. I’d learned about all the &lt;a href=&quot;/non-tech/2014/07/16/become-an-open-source-contributor/&quot;&gt;benefits of becoming a FOSS contributor&lt;/a&gt; and I wasn’t stopping anytime soon. I had every intention of still working on my FOSS project with or without GSoC. GSoC 2014 ended a week ago (August 18th 2014). There’s a list of things I still want to do with JSCommunicator and you can be sure I’ll keep working on them.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;P.S. This is not to say that programs like OPW and GSoC aren’t amazing programs. Try it out if you can, it’s really a great experience.&lt;/em&gt;&lt;/p&gt;

</description>
        <pubDate>Mon, 01 Sep 2014 10:06:52 +0000</pubDate>
        <link>http://julianalouback.com/tech/2014/09/01/how-i-became-a-debian-contributor/</link>
        <guid isPermaLink="true">http://julianalouback.com/tech/2014/09/01/how-i-became-a-debian-contributor/</guid>
        
        
        <category>tech</category>
        
      </item>
    
      <item>
        <title>JSCommunicator 2.0 (Beta) is Live!</title>
        <description>&lt;p&gt;This is the last week of Google Summer of Code 2014 - all good things must come to an end. To wrap things up, I’ve merged all my work on JSCommunicator into a new version with all the added features. You can now demo the new and improved (or at least so I hope) JSCommunicator on &lt;a href=&quot;https://rtc.debian.org/&quot;&gt;rtc.debian.org&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;JSCommunicator 2.0 has an assortment of new add-ons, the most important new features are the Instant Messaging component and the internationalization support.&lt;/p&gt;

&lt;p&gt;The UI has been reorganized but we are currently not using a skin for color scheme - will be posting about that in a bit. The idea is to have a more neutral look that can be easily customized and integrated with other web apps.&lt;/p&gt;

&lt;p&gt;A chat session is automatically opened when you begin a call with someone - unless you already started a chat session with said someone. Sound alerts for new incoming messages are optional in the config file, visual alerts occur when an inactive chat tab receives a new message. Future work includes multiple user chat sessions and adapting the layout to a large amount of chat tabs. Currently it only handles 6. &lt;em&gt;(Should I allow more? Who chats with more than 6 people at once? 14 year old me would, but now I just can’t handle that. Anyway, I welcome advice on how to go about this. Should we do infinite tabs or if not, what’s the cut-off?)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;About internationalization, I’m uber proud to say we currently run in 6 languages! The 6 are English (default), Spanish, French, Portuguese, Hebrew and German. But one thing I must mention is that since I added new stuff to JSCommunicator, some of the new stuff doesn’t have a translation. I took care of the Portuguese translation and Yehuda Korotkin quickly turned in the Hebrew translation, but we are still missing an update for Spanish, French and German. If you can contribute, please do. There are about 10 new labels to translate, you can fix the issue &lt;a href=&quot;https://github.com/opentelecoms-org/jscommunicator/issues/42&quot;&gt;here&lt;/a&gt;. Or if you’re short on time, shoot me an email with the translation for what’s on the right side of the ‘=’:&lt;/p&gt;

&lt;p&gt;welcome = Welcome,&lt;/p&gt;

&lt;p&gt;call = Call&lt;/p&gt;

&lt;p&gt;chat = Chat&lt;/p&gt;

&lt;p&gt;enter_contact = Enter contact&lt;/p&gt;

&lt;p&gt;type_to_chat = type to chat…&lt;/p&gt;

&lt;p&gt;start_chat = start chat&lt;/p&gt;

&lt;p&gt;me = me&lt;/p&gt;

&lt;p&gt;logout = Logout&lt;/p&gt;

&lt;p&gt;no_contact = Please enter a contact.&lt;/p&gt;

&lt;p&gt;remember_me = Remember me&lt;/p&gt;

&lt;p&gt;I’ll merge it myself but I’ll be sure to add you to the authors list.&lt;/p&gt;
</description>
        <pubDate>Thu, 14 Aug 2014 23:06:52 +0000</pubDate>
        <link>http://julianalouback.com/tech/2014/08/14/jscommunicator-2.0-is-live/</link>
        <guid isPermaLink="true">http://julianalouback.com/tech/2014/08/14/jscommunicator-2.0-is-live/</guid>
        
        
        <category>tech</category>
        
      </item>
    
      <item>
        <title>JSCommunicator - Setup and Architecture</title>
        <description>&lt;p&gt;&lt;em&gt;Preface&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;During Google Summer of Code 2014, I got to work on the Debian WebRTC portal under the mentorship of &lt;a href=&quot;http://danielpocock.com/&quot;&gt;Daniel Pocock&lt;/a&gt;. Now I had every intention of meticulously documenting my progress at each step of development in this blog, but I was a bit late in getting the blog up and running. I’ll now be publishing a series of posts to recap all I’ve done during GSoC. Better late than never.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Intro&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://jscommunicator.org&quot;&gt;JSCommunicator&lt;/a&gt; is a SIP communication tool developed in HTML and JavaScript. The code was designed to make integrating JSCommunicator with a website or web app as simple as possible. It’s quite easy, really. However, I do think a more detailed explanation on how to set things up and a guide to the JSCommunicator architecture could be of use, particularly for those wanting to modify the code in any way.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Setup&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;To begin, please &lt;a href=&quot;https://github.com/opentelecoms-org/jscommunicator.git&quot;&gt;fork the JSCommunicator repo&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you are new to git, feel free to follow the steps in section “Setup” and “Clone” in &lt;a href=&quot;/tutorial/2014/07/17/contribute-a-jscommunicator-translation/&quot;&gt;this post&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you read the README file (which you always should), you’ll see that JSCommunicator needs a SIP proxy that supports SIP over Websockets transport. Some options are:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;a href=&quot;http://www.resiprocate.org&quot;&gt;repro from reSIProcate&lt;/a&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;a href=&quot;http://www.kamailio.org&quot;&gt;Kamailio&lt;/a&gt;&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I didn’t find a tutorial for Kamailio setup, I did find one for &lt;a href=&quot;http://www.rtcquickstart.org/sip-proxy-installation/repro&quot;&gt;repro setup&lt;/a&gt;. And bonus, &lt;a href=&quot;http://danielpocock.com/get-webrtc-going-faster&quot;&gt;here&lt;/a&gt; you have a great tutorial on how to setup and configure your SIP proxy AND your TURN server.&lt;/p&gt;

&lt;p&gt;In your project’s root directory, you’ll see a file named &lt;strong&gt;config-sample.js&lt;/strong&gt;. Make a copy of that file named &lt;strong&gt;config.js&lt;/strong&gt;. The &lt;strong&gt;config-sample.js&lt;/strong&gt; file has comments that are very instructive. In sum, the only thing you &lt;em&gt;have&lt;/em&gt; to modify is the &lt;a href=&quot;https://github.com/opentelecoms-org/jscommunicator/blob/master/config-sample.js#L11-L13&quot;&gt;&lt;strong&gt;turn_servers&lt;/strong&gt;&lt;/a&gt; property and the &lt;a href=&quot;https://github.com/opentelecoms-org/jscommunicator/blob/master/config-sample.js#L16-L20&quot;&gt;&lt;strong&gt;websocket&lt;/strong&gt;&lt;/a&gt; property. In my case, &lt;strong&gt;debrtc.org&lt;/strong&gt; was the domain registered for testing my project, so my config file has:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot; data-lang=&quot;js&quot;&gt;&lt;span class=&quot;nx&quot;&gt;turn_servers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;server&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;turn:debrtc.org&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;     
&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Note that unlike the sample, I didn’t set a username and password so SIP credentials will be used.&lt;/p&gt;

&lt;p&gt;Now fill in the websocket property – here we use &lt;a href=&quot;http://www.sip5060.net/&quot;&gt;sip5060.net&lt;/a&gt;.&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot; data-lang=&quot;js&quot;&gt;&lt;span class=&quot;nx&quot;&gt;websocket&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nl&quot;&gt;servers&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'wss://ws.sip5060.net'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;connection_recovery_min_interval&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;connection_recovery_max_interval&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;30&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;I’m pretty sure you can set the connection_recovery properties to whatever you like. Everything else is optional. If you set the &lt;a href=&quot;https://github.com/opentelecoms-org/jscommunicator/blob/master/config-sample.js#L27-L34&quot;&gt;&lt;strong&gt;user&lt;/strong&gt;&lt;/a&gt; property, specifically &lt;strong&gt;display_name&lt;/strong&gt; and &lt;strong&gt;uri&lt;/strong&gt;, that will be used to fill in the Login form and takes preference over any ‘Remember me’ data. If you also set &lt;strong&gt;sip_auth_password&lt;/strong&gt;, JSCommunicator will automatically log in.&lt;/p&gt;

&lt;p&gt;All the other properties are for other optional functionalities and are well explained.&lt;/p&gt;

&lt;p&gt;You’ll need some third-party javascript that is not included in the JSCommunicator git repo. Namely jQuery version 1.4 or higher and ArbiterJS version 1.0. Download jQuery &lt;a href=&quot;http://jquery.com/download/&quot;&gt;here&lt;/a&gt; and ArbiterJS &lt;a href=&quot;http://arbiterjs.com/&quot;&gt;here&lt;/a&gt; and place the .js files in your project’s root directory. Do make sure that you are including the correct filename in your html file. For example, in &lt;a href=&quot;https://github.com/opentelecoms-org/jscommunicator/blob/master/phone-dev.shtml#L6&quot;&gt;&lt;strong&gt;phone-dev.shtml&lt;/strong&gt;&lt;/a&gt;, a file named &lt;strong&gt;jquery.js&lt;/strong&gt; is included. The file you downloaded will likely have version numbers in it’s file name. So rename the downloaded file or change the content of src in your includes. This is uber trivial, but I’ve made the mistake several times.&lt;/p&gt;

&lt;p&gt;You’ll also need JsSIP.js which can be downloaded &lt;a href=&quot;http://jssip.net/download/&quot;&gt;here&lt;/a&gt;. Same naming care must be taken as is the case for jQuery and ArbiterJS. The recently added Instant Messaging component and some of the new features need &lt;a href=&quot;http://jqueryui.com/download/&quot;&gt;jQuery-UI&lt;/a&gt; - so far version 1.11.* is known to work. From the downloaded .zip all you need is the jquery-ui-&lt;em&gt;.&lt;/em&gt;.&lt;em&gt;.js file and the jquery-ui-&lt;/em&gt;.&lt;em&gt;.&lt;/em&gt;.css file, also to be placed in the project’s root directory. If you’ll be using the internationalization support you’ll also need &lt;a href=&quot;https://github.com/jquery-i18n-properties/jquery-i18n-properties/blob/master/jquery.i18n.properties.js&quot;&gt;jquery.i18n.properties.js&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To try out JSCommunicator, deploy the website locally by copying your project directory to the apache document root directory (provided you are using &lt;a href=&quot;http://www.apache.org/&quot;&gt;apache&lt;/a&gt;, which is a good idea.). You’ll likely have to restart your server before this works. Now the demo .shtml pages only have a header with all the necessary includes, then a Server Side Include for the body of the page, with all the JSCommunicator html. The html content is in the file &lt;strong&gt;jscommunicator.inc&lt;/strong&gt;. You can &lt;a href=&quot;http://httpd.apache.org/docs/2.2/howto/ssi.html&quot;&gt;enable SSI&lt;/a&gt; on apache, OR you can simply copy and paste the content of &lt;strong&gt;jscommunicator.inc&lt;/strong&gt; into phone-dev.shmtl. Start up apache, open a browser and navigate to &lt;strong&gt;localhost/jscommunicator/phone-dev.shmtl&lt;/strong&gt; and you should see:
&lt;img src=&quot;/assets/jscommunicatorRaw.jpg&quot; alt=&quot;jscommunicatorRaw&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Actually, pending updates to JSCommunicator, you should see a brand new UI! But all the core stuff will be the same.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Architecture&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclaimer: I’m explaining my view of the JSCommunicator architecture, which currently may not be 100% correct. But so far it’s been enough for me to make my modifications and additions to the code, so it could be of use. One I get a JSCommunicator Founding Father’s stamp of approval, I’ll be sure to confirm the accuracy.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Now to explain how the JSCommunicator code interacts, the use of each code ‘item’ is described, ignoring all the html and css which will vary according to how you choose to use JSCommunicator. I’m also not going to explain jQuery which is a dependency but not specific to WebRTC. The core JSCommunicator code is the following:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;config.js&lt;/li&gt;
  &lt;li&gt;jssip-helper.js&lt;/li&gt;
  &lt;li&gt;parseuri.js&lt;/li&gt;
  &lt;li&gt;webrtc-check.js&lt;/li&gt;
  &lt;li&gt;JSCommUI.js&lt;/li&gt;
  &lt;li&gt;JSCommManager.js&lt;/li&gt;
  &lt;li&gt;make-release&lt;/li&gt;
  &lt;li&gt;init.js&lt;/li&gt;
  &lt;li&gt;Arbiter.js&lt;/li&gt;
  &lt;li&gt;JsSIP.js&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each of these files will be presented in what I hope is an intuitive order.&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;config.js&lt;/strong&gt; - As expected, this file contains your custom configuration specifications, i.e. the servers being used to direct traffic, authentication credentials, and a series of properties to enable/disable optional functionalities in JSCommunicator.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The next three files could be considered ‘utils’:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;jssip-helper.js&lt;/strong&gt; - This will load the data from config.js necessary to run JSCommunicator, such as configurations relating to servers, websockets, connection specifications (timeout, recovery intervals), and user credentials. Properties for optional features are ignored of course.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;parseuri.js&lt;/strong&gt; - Contains a function to segregate data from a URI.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;webrtc-check.js&lt;/strong&gt; - Verifies if browser is WebRTC compatible by checking if it’s possible to enable camera and microphone access.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These two are where the magic happens:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;JSCommUI.js&lt;/strong&gt; - Responsible for the UI component, controling what becomes visible to the user, audio effects, client-side error handling, and gathering the data that will be fed to JSCommManager.js.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;JSCommManager.js&lt;/strong&gt; - Initializes the SIP User Agent to manage the session including (but not limited to) beginning/terminating the session, sending/receiving SIP messages and calls, and signaling the state of the session and other important notifications such as incoming calls, messages received, etc.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now for some extras:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;make-release&lt;/strong&gt; - Combines the main .js files into one big file. Gets jssip-helper.js, parseuri.js, webrtc-check.js, JSCommUI.js and JSCommManager.js and spits out a new file JSComm.js with all that content. Now you understand why &lt;strong&gt;phone-dev.shtml&lt;/strong&gt; included each of the 5 files mentioned above whereas &lt;strong&gt;phone.shmtl&lt;/strong&gt; includes only JSComm.js which didn’t exist until you ran make-release. That confused me a bit.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;init.js&lt;/strong&gt; - On page load, calls JSCommManager.js’ init function, which eventually calls JSCommUI.js’ init function. In other words, starts up the JSCommunicator app. I guess it’s only used to show how to start up the app. This could be done directly on the page you’re embedding JSCommunicator from. So maybe not entirely needed.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Third party code:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;Arbiter.js&lt;/strong&gt; - Javascript implmentation of the publish/subscribe patten, written by &lt;a href=&quot;http://mattkruse.com/&quot;&gt;Matt Kruse&lt;/a&gt;. In JSCommunicator it’s used in JSCommManager.js to publish signals to describe direct the app’s behavior. For example, JSCommManager will publish a message indicating that the user received a call from a certain URI. In event-demo.js we subscribe to this kind of message and when said message is received, an action can be performed such as adding to the app’s call history. Very cool.&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;&lt;strong&gt;JsSIP.js&lt;/strong&gt; - Implements the SIP WebSocket transport in Javascript. This ensures the transportantion of data is done in adherence to the &lt;a href=&quot;https://tools.ietf.org/html/rfc7118&quot;&gt;WebSocket protocol&lt;/a&gt;. In JSCommManager.js we initialize a SIP User Agent based in the implementation in JsSIP.js. The User Agent will ‘translate’ all of the JSCommunicator actions into SIP WebSocket format. For example, when sending an IM, the JSCommunicator app will collect the essential data such as say the origin URI, destination URI and an IM message, while the User Agent is in charge of formating the data so that it can be transported in a SIP message unit. A SIP message contains a lot more information than just the sender, receiver and message. Of course, a lot of the info in a SIP message is irrelevant to the user and in JSCommUI.js we filter through all that data and only display what the user needs to see.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here’s a diagram of sorts to help you visualize how the code interacts:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/jscommArch.jpg&quot; alt=&quot;jscommArch&quot; /&gt;&lt;/p&gt;

&lt;p&gt;In sum, 1 - JSCommUI.js handles what is displayed in the UI and feeds data to JSCommManager.js; 2 - JSCommManager.js &lt;em&gt;actually does stuff&lt;/em&gt;, feeding data to be displayed to JSCommUI.js; 3 - JSCommManager.js calls functions from the three ‘utils’, parseuri.js, webrtc-check.js and jssip-helpher.js which organizes the data from config.js; 4 - JSCommManager.js initializes a SIP User Agent based on the implementation in Arbiter.js.&lt;/p&gt;

&lt;p&gt;When making any changes to JSCommunicator, you will likely only be working with JSCommUI.js and JSCommManager.js.&lt;/p&gt;

</description>
        <pubDate>Mon, 11 Aug 2014 15:06:52 +0000</pubDate>
        <link>http://julianalouback.com/tech/2014/08/11/jscommunicator-setup-and-architecture/</link>
        <guid isPermaLink="true">http://julianalouback.com/tech/2014/08/11/jscommunicator-setup-and-architecture/</guid>
        
        
        <category>tech</category>
        
      </item>
    
      <item>
        <title>Kickstarting the JSCommunicator-xTuple Extension</title>
        <description>&lt;p&gt;&lt;a href=&quot;http://www.xtuple.com/&quot;&gt;xTuple&lt;/a&gt; is in my opinion incredibly well designed; the code is clean and the architecture ahderent to a standardized structure. All this makes working with xTuple software quite a breeze.&lt;/p&gt;

&lt;p&gt;I wanted to integrate &lt;a href=&quot;https://github.com/opentelecoms-org/jscommunicator&quot;&gt;JSCommunicator&lt;/a&gt; into the web-based xTuple version. JSCommunicator is a SIP communication tool, so my first step was to create an extension for the SIP account data. Luckily for me, the xTuple development team published an awesome &lt;a href=&quot;https://github.com/xtuple/xtuple-extensions/blob/master/docs/TUTORIAL.md&quot;&gt;tutorial&lt;/a&gt; for writing an xTuple extension.&lt;/p&gt;

&lt;p&gt;xTuple cleverly uses model based business objects for the various features available. This makes customizing xTuple very straightforward. I used the tutorial mentioned above for writing my extension, but soon noticed my goals were a little different. A SIP account has 3 data fields, these being the SIP URI, the account password and an optional display name. xTuple currently has a business object in the core code for a User Account and it would make a lot more sense to simply add my 3 fields to this existing business object rather than create another business object. The tutorial very clearly shows how to extend a business object with another business object, but not how to extend a business object with only new fields (not a whole new object).&lt;/p&gt;

&lt;p&gt;Now maybe I’m just a whole lot slower than most people, but I had a ridiculously had time figuring this out. Mind you, this is because I’m slow, because the xTuple documentation and code is understandable and as self-explanatory as it gets. I think it just takes a bit to get used to. Either way, I thought this just might be useful to others so here is how I went about it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Setup&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;First you’ll have to set up your xTuple development environment and fork the xtuple and xtuple-extesions repositories as shown in &lt;a href=&quot;https://github.com/xtuple/xtuple-vagrant/blob/master/README.md&quot;&gt;this handy tutorial&lt;/a&gt;. A footnote I’d like to add is please verify that your version of Vagrant (and anything else you install) is the one listed in the tutorial. I think I spent like two entire days or more on a wild goose (bug) chase trying to set up my environment when the cause of all the errors was that I somehow installed an older version of Vagrant - 1.5.4 instead of 1.6.3. Please don’t make the same mistake I did. Actually if for some reason you get the following error when you try using node:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot; data-lang=&quot;sh&quot;&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;ERROR&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt; 2014-07-10T23:52:46.948Z&amp;gt;&amp;gt; Unrecoverable exception. Cannot call method 'extend' of undefined

    at /home/vagrant/dev/xtuple/lib/backbone-x/source/model.js:37:39

    at Object.&amp;lt;anonymous&amp;gt; (/home/vagrant/dev/xtuple/lib/backbone-x/source/model.js:1364:3)
    ...&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;chances are, you have the wrong version. That’s what happened to me. The Vagrant Virtual Development Environment automatically installs and configures everything you need, it’s ready to go. So if you find yourself installing and updating and apt-gets and etc, you probably did something wrong.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Coding&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;So by now we should have the Vagrant Virtual Development Environment set up and the web app up and running and accessible at localhost:8443. So far so good.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclaimer: You will note that much of this is similar - or rather, nearly identical - to xTuple’s &lt;a href=&quot;https://github.com/xtuple/xtuple-extensions/blob/master/docs/TUTORIAL.md&quot;&gt;tutorial&lt;/a&gt; but there are some small but important differences and a few observations I think might be useful. Other Disclaimer: I’m describing how I did it, which may or may not be ‘up to snuff’. Works for me though.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Schema&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;First let’s make a schema for the table we will create with the new custom fields. Be sure to create the correct directory stucture, aka &lt;strong&gt;/path/to/xtuple-extensions/source/&amp;lt;YOUR EXTENSION NAME&amp;gt;/database/source&lt;/strong&gt; or in my case &lt;strong&gt;/path/to/xtuple-extensions/source/sip_account/database/source&lt;/strong&gt;, and create the file &lt;strong&gt;create_sa_schema.sql&lt;/strong&gt;, ‘sa’ is the name of my schema. This file will contain the following lines:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-sql&quot; data-lang=&quot;sql&quot;&gt;&lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;$$&lt;/span&gt;
  &lt;span class=&quot;cm&quot;&gt;/* Only create the schema if it hasn't been created already */&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;res&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;sql&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;&quot;select schema_name from information_schema.schemata where schema_name = 'sa'&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;res&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;plv8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;execute&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;sql&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;res&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;err&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;sql&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;&quot;create schema sa; grant all on schema sa to group xtrole;&quot;&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;plv8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;execute&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;sql&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;err&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;err&quot;&gt;$$&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;language&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;plv8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Of course, feel free to replace ‘sa’ with your schema name of choice. All the code described here can be found in my xtuple-extensions fork, on the &lt;a href=&quot;https://github.com/JLouback/xtuple-extensions/tree/sip_ext&quot;&gt;sip_ext branch&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Table&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We’ll create a table containing your custom fields and a link to an existing table - the table for the existing business object you want to extend. If you’re wondering why make a whole new table for a few extra fields, here’s a good &lt;a href=&quot;https://github.com/xtuple/xtuple-extensions/blob/master/docs/TUTORIAL-FAQ.md#why-do-we-need-a-new-table-to-extend-contact&quot;&gt;explanation&lt;/a&gt;, the case in question is adding fields to the Contact business object.&lt;/p&gt;

&lt;p&gt;You need to first figure out what table you want to link to. This might not be uber easy. I think the best way to go about it is to look at the ORMs. The xTuple ORMs are a JSON mapping between the SQL tables and the object-oriented world above the database, they’re .json files found at &lt;strong&gt;path/to/xtuple/node_modules/xtuple/enyo-client/database/orm/models&lt;/strong&gt; for the core business objects and at &lt;strong&gt;path/to/xtuplenyo-client/extensions/source/&amp;lt;EXTENSION NAME&amp;gt;/database/orm/models&lt;/strong&gt; for exension business objects. I’ll give two examples. If you look at &lt;a href=&quot;https://github.com/xtuple/xtuple/blob/master/enyo-client/database/orm/models/contact.json#L6&quot;&gt;contact.json&lt;/a&gt; you will see that the Contact business object refers to the table “cntct”. Look for the “type”: “Contact” on the &lt;a href=&quot;https://github.com/xtuple/xtuple/blob/master/enyo-client/database/orm/models/contact.json#L5&quot;&gt;line above&lt;/a&gt;, so we know it’s the “Contact” business object. In my case, I wanted to extend the UserAccount and UserAccountRelation business objects, so check out &lt;a href=&quot;https://github.com/xtuple/xtuple/blob/master/enyo-client/database/orm/models/user_account.json&quot;&gt;user_account.json&lt;/a&gt;. The table listed for &lt;a href=&quot;https://github.com/xtuple/xtuple/blob/master/enyo-client/database/orm/models/user_account.json#L314&quot;&gt;UserAccount is xt.usrinfo&lt;/a&gt; and the table listed for &lt;a href=&quot;https://github.com/xtuple/xtuple/blob/master/enyo-client/database/orm/models/user_account.json#L448&quot;&gt;UserAccountRelation is xt.usrlite&lt;/a&gt;. A closer look at the sql files for these tables (&lt;a href=&quot;https://github.com/xtuple/xtuple/blob/master/enyo-client/database/source/xt/views/usrinfo.sql&quot;&gt;usrinfo.sql&lt;/a&gt; and &lt;a href=&quot;https://github.com/xtuple/xtuple/blob/master/enyo-client/database/source/xt/tables/usrlite.sql&quot;&gt;usrlite.sql&lt;/a&gt;) revealed that usrinfo is in fact a view and usrlite is ‘A light weight table of user information used to avoid punishingly heavy queries on the public usr view’. I chose to refer to xt.usrlite - that or I received error messages when trying the other table names.&lt;/p&gt;

&lt;p&gt;Now I’ll make the file &lt;strong&gt;/path/to/xtuple-extensions/source/sip_account/database/source/usrlitesip.sql&lt;/strong&gt;,  to create a table with my custom fields plus the link to the urslite table. Don’t quote me on this, but I’m under the impression that this is the norm for naming the sql file joining tables: the name of the table you are referring to (‘usrlite’ in this case) and your extension’s name. 
Content of &lt;strong&gt;usrlitesip.sql&lt;/strong&gt;:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-sql&quot; data-lang=&quot;sql&quot;&gt;&lt;span class=&quot;k&quot;&gt;select&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;xt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;create_table&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'usrlitesip'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'sa'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;select&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;xt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;add_column&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'usrlitesip'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'usrlitesip_id'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'serial'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'primary key'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'sa'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;select&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;xt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;add_column&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'usrlitesip'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'usrlitesip_usr_username'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'text'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'references xt.usrlite (usr_username)'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'sa'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;select&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;xt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;add_column&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'usrlitesip'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'usrlitesip_uri'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'text'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;''&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'sa'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;select&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;xt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;add_column&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'usrlitesip'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'usrlitesip_name'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'text'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;''&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'sa'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;select&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;xt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;add_column&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'usrlitesip'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'usrlitesip_password'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'text'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;''&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'sa'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;comment&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;on&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;table&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sa&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;usrlitesip&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;is&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'Joins User with SIP account'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Breaking it down, line 1 creates the table named ‘usrlitesip’ (no duh), line 2 is for the primary key (self-explanatory). You can then add any columns you like, just be sure to add one that references the table you want to link to. I checked &lt;a href=&quot;https://github.com/xtuple/xtuple/blob/master/enyo-client/database/source/xt/tables/usrlite.sql#L3&quot;&gt;usrlite.sql and saw the primary key is usr_username&lt;/a&gt;, be sure to use the primary key of the table you are referencing.&lt;/p&gt;

&lt;p&gt;You can check what you made by executing the .sql files like so:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot; data-lang=&quot;sh&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;cd&lt;/span&gt; /path/to/xtuple-extensions/source/sip_account/database/source
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;psql &lt;span class=&quot;nt&quot;&gt;-U&lt;/span&gt; admin &lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt; dev &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt; create_sa_schema.sql
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;psql &lt;span class=&quot;nt&quot;&gt;-U&lt;/span&gt; admin &lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt; dev &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt; usrlitesip.sql&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;After which you will see the table with the columns you created if you enter:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot; data-lang=&quot;sh&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;psql &lt;span class=&quot;nt&quot;&gt;-U&lt;/span&gt; admin &lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt; dev &lt;span class=&quot;nt&quot;&gt;-c&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;select * from sa.usrlitesip;&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Now create the file &lt;strong&gt;/path/to/xtuple-extensions/source/sip_account/database/source/manifest.js&lt;/strong&gt; to put the files together and in the right order. It should contain:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot; data-lang=&quot;js&quot;&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;s2&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;sip_account&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;s2&quot;&gt;&quot;version&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;1.4.1&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;s2&quot;&gt;&quot;comment&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Sip Account extension&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;s2&quot;&gt;&quot;loadOrder&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;999&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;s2&quot;&gt;&quot;dependencies&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;crm&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;
  &lt;span class=&quot;s2&quot;&gt;&quot;databaseScripts&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;s2&quot;&gt;&quot;create_sa_schema.sql&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s2&quot;&gt;&quot;usrlitesip.sql&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s2&quot;&gt;&quot;register.sql&quot;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;I think the “name” has to be the same you named your extension directory as in &lt;strong&gt;/path/to/xtuple-extensions/source/&amp;lt;YOUR EXTENSION NAME&amp;gt;&lt;/strong&gt;. I think the “comment” can be anything you like and you want your “loadOrder” to be high so it’s the last thing installed (as it’s an add on.) So far we are doing exactly what’s instructed in the xTuple tutorial. It’s repetitive, but I think you can never have too many examples to compare to. In “databaseScripts” you will list the two .sql files you just created for the schema and the table, plus another file to be made in the same directory named &lt;strong&gt;register.sql&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I’m not sure why you have to make the &lt;strong&gt;register.sql&lt;/strong&gt; or even if you indeed have to. If you leave the file empty, there will be a build error, so put a ‘;’ in the &lt;strong&gt;register.sql&lt;/strong&gt; or remove the line &lt;strong&gt;“register.sql”&lt;/strong&gt; from &lt;strong&gt;manifest.js&lt;/strong&gt; as I think for now we are good without it.&lt;/p&gt;

&lt;p&gt;Now let’s update the database with our new extension:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot; data-lang=&quot;sh&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;cd&lt;/span&gt; /path/to/xtuple
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;./scripts/build_app.js &lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt; dev &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; ../xtuple-extensions/source/sip_account
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;psql &lt;span class=&quot;nt&quot;&gt;-U&lt;/span&gt; admin &lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt; dev &lt;span class=&quot;nt&quot;&gt;-c&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;select * from xt.ext;&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;That last command should display a table with a list of extensions; the ones already in xtuple like ‘crm’ and ‘billing’ and some others plus your new extension, in this case ‘sip_account’. When you run &lt;strong&gt;build_app.js&lt;/strong&gt; you’ll probably see a message along the lines of “&amp;lt;Extension name&amp;gt; has no client code, not building client code” and that’s fine because yeah, we haven’t worked on the client code yet.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ORM&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here’s where things start getting different. So ORMs link your object to an SQL table. But we DON’T want to make a &lt;em&gt;new&lt;/em&gt; business object, we want to extend an &lt;em&gt;existing&lt;/em&gt; business object, so the ORM we will make will be a little different than the xTuple tutorial. &lt;a href=&quot;https://github.com/shackbarth&quot;&gt;Steve Hackbarth&lt;/a&gt; kindly explained this new business object/existing business object ORM concept &lt;a href=&quot;https://github.com/xtuple/xtuple/issues/1685&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;First we’ll create the directory &lt;strong&gt;/path/to/xtuple-extensions/source/sip_account/database/orm/ext&lt;/strong&gt;, according to xTuple convention. ORMs for new business objects would be put in &lt;strong&gt;/path/to/xtuple-extensions/source/sip_account/database/orm/models&lt;/strong&gt;. Now we’ll create the .json file &lt;strong&gt;/path/to/xtuple-extensions/source/sip_account/database/orm/ext/user_account.jscon&lt;/strong&gt; for our ORM. Once again, don’t quote me on this, but I think the name of the file should be the name of the business object you are extending, as is done in the &lt;a href=&quot;https://github.com/xtuple/xtuple-extensions/blob/master/sample/icecream/database/orm/ext/contact.json&quot;&gt;turorial example extending the Contact object&lt;/a&gt;. In our case, UserAccount is defined in user_account.json and that’s what I named my extension ORM too.
Here’s what you should place in it:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-json&quot; data-lang=&quot;json&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;context&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;sip_account&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;nameSpace&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;XM&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;UserAccount&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;table&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;sa.usrlitesip&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;isExtension&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;isChild&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;comment&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Extended by Sip&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;relations&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;column&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;usrlitesip_usr_username&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;inverse&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;username&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;properties&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;uri&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;attr&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;String&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;column&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;usrlitesip_uri&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;isNaturalKey&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;displayName&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;attr&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;String&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;column&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;usrlitesip_name&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;sipPassword&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;attr&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;String&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;column&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;usrlitesip_password&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;isSystem&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;context&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;sip_account&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;nameSpace&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;XM&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;UserAccountRelation&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;table&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;sa.usrlitesip&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;isExtension&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;isChild&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;false&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;comment&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Extended by Sip&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;relations&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;column&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;usrlitesip_usr_username&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;inverse&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;username&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;properties&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;uri&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;attr&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;String&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;column&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;usrlitesip_uri&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;isNaturalKey&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;displayName&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;attr&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;String&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;column&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;usrlitesip_name&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;sipPassword&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;attr&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;String&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
          &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;column&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;usrlitesip_password&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;isSystem&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Note the “context” is my extension name, because the context + nameSpace + type combo has to be unique. We already have a UserAccount and UserAccountRelation object in the “XM” namespace in the “xtuple” context in the original &lt;a href=&quot;https://github.com/xtuple/xtuple/blob/master/enyo-client/database/orm/models/user_account.json#L310&quot;&gt;user_account.json&lt;/a&gt;, now we will have a UserAccount and UserAccountRelation object in the “XM” namespace in the “sip_account” conext. What else is important? Note that “isExtension” is &lt;strong&gt;true&lt;/strong&gt; on lines 7 and 47 and the “relations” item contains the “column” of the foreign key we referenced.&lt;/p&gt;

&lt;p&gt;This is something you might want to verify: “column” (lines 12 and 52) is the name of the attribute on &lt;em&gt;your&lt;/em&gt; table. When we made a reference to the primary key usr_usrname from the xt.usrlite table we named that column usrlitesip_usr_usrname. But the “inverse” is the attribute name associated with the original sql column in the original ORM. Did I lose you? I had a lot of trouble with this silly thing. In the original ORM that created a new UserAccount business object, the primary key attribute is named “username”, as can be seen &lt;a href=&quot;https://github.com/xtuple/xtuple/blob/master/enyo-client/database/orm/models/user_account.json#L326-331&quot;&gt;here&lt;/a&gt;. That is what should be used for the “inverse” value. Not the sql column name (usr_username) but the object attribute name (username). I’m emphasizing this because I made that mistake and if I can spare you the pain I will.&lt;/p&gt;

&lt;p&gt;If we rebuild our extension everything should come along nicely, but you won’t see any changes just yet in the web app because we haven’t created the client code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Client&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Create the directory &lt;strong&gt;/path/to/xtuple-extensions/source/sip_account/client&lt;/strong&gt; which is where we’ll keep all the client code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Extend Workspace View&lt;/strong&gt;
I want the fields I added to show up on the form to create a new User Account, so I need to extend the view for the User Account workspace. I’ll start by creating a directory &lt;strong&gt;/path/to/xtuple-extensions/source/sip_account/client/views&lt;/strong&gt; and in it creating a file named ‘workspace.js’ containing this code:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot; data-lang=&quot;js&quot;&gt;&lt;span class=&quot;nx&quot;&gt;XT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;extensions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;sip_account&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;initWorkspace&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;

	&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;extensions&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
  	&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;kind&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;onyx.GroupboxHeader&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;container&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;mainGroup&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;_sipAccount&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;loc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()},&lt;/span&gt;
  	&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;kind&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;XV.InputWidget&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;container&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;mainGroup&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;attr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;uri&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
  	&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;kind&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;XV.InputWidget&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;container&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;mainGroup&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;attr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;displayName&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
  	&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;kind&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;XV.InputWidget&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;container&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;mainGroup&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;type&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;password&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;attr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;sipPassword&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
	&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;

	&lt;span class=&quot;nx&quot;&gt;XV&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;appendExtension&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;XV.UserAccountWorkspace&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;extensions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;So I’m initializing my workspace and creating an array of items to add (append) to view XV.UserAccountWorkspace. The first ‘item’ is this onyx.GroupboxHeader which is a pretty divider for my new form fields, the kind you find in the web app at Setup &amp;gt; User Accounts, like ‘Overview’. I have no idea what other options there are for container other than “mainGroup”, so let’s stick to that. I’ll explain &lt;strong&gt;content: “_sipAccount”.loc()&lt;/strong&gt; in a bit. Next I created three input fields of the XV.InputWidget kind. This also confused me a bit as there are different kinds of input to be used, like dropdowns and checkboxes. The only advice I can give is snoop around the webapp, find an input you like and look up the corresponding workspace.js file to see what was used.&lt;/p&gt;

&lt;p&gt;What we just did is (should be) enough for the new fields to show up on the User Account form. But before we see things change, we have to package the client. Create the file &lt;strong&gt;/path/to/xtuple-extensions/source/sip_account/client/views/package.js&lt;/strong&gt;. This file is needed to ‘package’ groups of files and indicates the order the files should be loaded (for more on that, see &lt;a href=&quot;https://github.com/enyojs/enyo/wiki/Tutorial#you-got-to-keep-it-separated&quot;&gt;this&lt;/a&gt;). For now, all the file will contain is:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot; data-lang=&quot;js&quot;&gt;&lt;span class=&quot;nx&quot;&gt;enyo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;depends&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
&lt;span class=&quot;s2&quot;&gt;&quot;workspace.js&quot;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;You also need to package the ‘views’ directory containing &lt;strong&gt;workspace.js&lt;/strong&gt;, so create the file Create the file &lt;strong&gt;/path/to/xtuple-extensions/source/sip_account/client/package.js&lt;/strong&gt; and in it show that the directory ‘views’ and its contents must be part of the higher level package:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot; data-lang=&quot;js&quot;&gt;&lt;span class=&quot;nx&quot;&gt;enyo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;depends&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
&lt;span class=&quot;s2&quot;&gt;&quot;views&quot;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;I like to think of it as a box full of smaller boxes.&lt;/p&gt;

&lt;p&gt;This will sound terrible, but apparently you also need to create the file &lt;strong&gt;/path/to/xtuple-extensions/source/sip_account/client/core.js&lt;/strong&gt; containing this line:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot; data-lang=&quot;js&quot;&gt;&lt;span class=&quot;nx&quot;&gt;XT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;extensions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;icecream&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{};&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;I don’t know why. As soon as I find out I’ll be sure to inform you.&lt;/p&gt;

&lt;p&gt;As we’ve added a file to the client directory, be sure to update  &lt;strong&gt;/path/to/xtuple-extensions/source/sip_account/client/package.js&lt;/strong&gt; so it included the new file:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot; data-lang=&quot;js&quot;&gt;&lt;span class=&quot;nx&quot;&gt;enyo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;depends&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
&lt;span class=&quot;s2&quot;&gt;&quot;core.js&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;s2&quot;&gt;&quot;views&quot;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;&lt;strong&gt;Translations&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Remember &lt;strong&gt;“_sipAccount”.loc()”&lt;/strong&gt; in our &lt;strong&gt;workspace.js&lt;/strong&gt; file? xTuple has great internationalization support and it’s easy to use. Just create the directory and file &lt;strong&gt;/path/to/xtuple-extensions/source/sip_account/client/en/strings.js&lt;/strong&gt; and in it put key-value pairs for labels and their translation, like this:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot; data-lang=&quot;js&quot;&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;s2&quot;&gt;&quot;use strict&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;lang&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;XT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;stringsFor&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;en_US&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;s2&quot;&gt;&quot;_sipAccount&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Sip Account&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s2&quot;&gt;&quot;_uri&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Sip URI&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s2&quot;&gt;&quot;_displayName&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Display Name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;s2&quot;&gt;&quot;_sipPassword&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Password&quot;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;typeof&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;exports&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!==&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'undefined'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;exports&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;language&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;lang&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}());&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;So far I included all the labels I used in my Sip Account form. If you write the wrong label (key) or forget to include a corresponding key-value pair in &lt;strong&gt;strings.js&lt;/strong&gt;, xTuple will simply name your lable “_lableName”, underscore and all.&lt;/p&gt;

&lt;p&gt;Now build your extension and start up the server:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-sh&quot; data-lang=&quot;sh&quot;&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;cd&lt;/span&gt; /path/to/xtuple 
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;./scripts/build_app.js &lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt; dev &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; ../xtuple-extensions/source/sip_account
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;node node-datasource/main.js&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;If the server is already running, just stop it and restart it to reflect your changes.&lt;/p&gt;

&lt;p&gt;Now if you go to Setup &amp;gt; User Accounts and click the “+” button, you should see a nice little addition to the form with a ‘Sip Account’ divider and three new fields. Nice, eh?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Extend Parameters&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Currently you can search your User Accounts list using any of the User Account fields. It would be nice to be able to search with the Sip account fields we added as well. To do that, let’s create the directory &lt;strong&gt;/path/to/xtuple-extensions/source/sip_account/client/widgets&lt;/strong&gt; and there create the file &lt;strong&gt;parameter.js&lt;/strong&gt; to extend XV.UserAccountListParameters. One again, you’ll have to look this up. In the &lt;em&gt;xTuple&lt;/em&gt; code you’ll find the application’s &lt;strong&gt;parameter.js&lt;/strong&gt; in &lt;a href=&quot;https://github.com/xtuple/xtuple/tree/master/enyo-client/application/source/widgets&quot;&gt;/path/to/xtuple/enyo-client/application/source/widgets&lt;/a&gt;. Search for the business object you are extending (for example, XV.UserAccount) and look for some combination of the business object name and ‘Parameters’. If there’s  more than one, try different ones. Not a very refined method, but it worked for me.
Here’s the content of our &lt;strong&gt;parameter.js&lt;/strong&gt;:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot; data-lang=&quot;js&quot;&gt;&lt;span class=&quot;nx&quot;&gt;XT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;extensions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;sip_account&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;initParameterWidget&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;

  &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;extensions&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;kind&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;onyx.GroupboxHeader&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;content&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;_sipAccount&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;loc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()},&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;uri&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;label&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;_uri&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;loc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;attr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;uri&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;defaultKind&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;XV.InputWidget&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;displayName&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;label&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;_displayName&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;loc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;attr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;displayName&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;defaultKind&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;XV.InputWidget&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;

  &lt;span class=&quot;nx&quot;&gt;XV&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;appendExtension&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;XV.UserAccountListParameters&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;extensions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Node that I didn’t include a search field for the password attribute for obvious reasons. Now once again, we package this new code addition by creating a &lt;strong&gt;/path/to/xtuple-extensions/source/sip_account/client/widgets/package.js&lt;/strong&gt; file:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot; data-lang=&quot;js&quot;&gt;&lt;span class=&quot;nx&quot;&gt;enyo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;depends&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
&lt;span class=&quot;s2&quot;&gt;&quot;parameter.js&quot;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;We also have to update &lt;strong&gt;/path/to/xtuple-extensions/source/sip_account/client/package.js&lt;/strong&gt;:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot; data-lang=&quot;js&quot;&gt;&lt;span class=&quot;nx&quot;&gt;enyo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;depends&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
&lt;span class=&quot;s2&quot;&gt;&quot;core.js&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;s2&quot;&gt;&quot;widgets&quot;&lt;/span&gt;
&lt;span class=&quot;s2&quot;&gt;&quot;views&quot;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Rebuild the extension (and restart the server) and go to Setup &amp;gt; User Accounts. Press the magnifying glass button on the upper left side of the screen and you’ll see many options for filtering the User Accounts, among them the SIP Uri and Display Name.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Extend List View&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You might want your new fields to show up on the list of User Accounts. There’s a bit of an issue here because unlike what we did in &lt;strong&gt;workspace.js&lt;/strong&gt; and &lt;strong&gt;parameter.js&lt;/strong&gt;, we can’t append new things to the list of UserAccounts with the funciton &lt;strong&gt;XV.appendExtension(args)&lt;/strong&gt;. First I tried overwriting the original UserAccountList, which works but it’s far from ideal as this could result in a loss of data from the core implementation. After some &lt;a href=&quot;https://github.com/xtuple/xtuple/issues/1703#issuecomment-50115385&quot;&gt;discussion with the xTuple dev community&lt;/a&gt;, now there’s a better alternative:&lt;/p&gt;

&lt;p&gt;Create the file &lt;strong&gt;/path/to/xtuple-extensions/source/sip_account/client/views/list.js&lt;/strong&gt; and add the following:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot; data-lang=&quot;js&quot;&gt;&lt;table class=&quot;rouge-table&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;gutter gl&quot;&gt;&lt;pre class=&quot;lineno&quot;&gt;1
2
3
4
5
6
7
8
9
&lt;/pre&gt;&lt;/td&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;oldUserAccountListCreate&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;XV&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;UserAccountList&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;prototype&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;XV&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;UserAccountList&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;prototype&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;create&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;oldUserAccountListCreate&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;apply&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kr&quot;&gt;arguments&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;createComponent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;kind&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;XV.ListColumn&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;container&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;$&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;listItem&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;components&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;
       &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;kind&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;XV.ListAttr&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;attr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;uri&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
   &lt;span class=&quot;p&quot;&gt;]})&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;To understand what I’m doing, check out the &lt;strong&gt;XV.UserAccountList&lt;/strong&gt; implementation in &lt;a href=&quot;https://github.com/xtuple/xtuple/blob/master/enyo-client/application/source/views/list.js#L2512-L2535&quot;&gt;/path/to/xtuple/enyo-client/application/source/views/list.js&lt;/a&gt; – the entire highlighted part. What we are doing is ‘extending’ XV.UserAccountList through ‘prototype-chaining’; this is how &lt;a href=&quot;http://enyojs.com/docs/2.4.0/key-concepts/kinds.html&quot;&gt;inheritance works with Enyo&lt;/a&gt;. In line 1 we create a prototype and in line 4 we inherit the features including original components array which the list is based on. We then create an additional component immitating the setup shown in &lt;strong&gt;XV.UserAccountList&lt;/strong&gt;: An &lt;strong&gt;XV.ListColumn&lt;/strong&gt; containing an &lt;strong&gt;XV.ListAttr&lt;/strong&gt;, which should be placed in the &lt;strong&gt;XV.ListItem&lt;/strong&gt; components array as is done with the existing columns (refer to &lt;a href=&quot;https://github.com/xtuple/xtuple/blob/master/enyo-client/application/source/views/list.js#L2512-L2535&quot;&gt;implementation&lt;/a&gt;). Components can or should (?) have &lt;a href=&quot;http://enyojs.com/docs/2.4.0/key-concepts/components.html&quot;&gt;names&lt;/a&gt; which are used to access said components. You’d refer to a specific component by the &lt;strong&gt;this.$.componentName&lt;/strong&gt; hash. The components in &lt;strong&gt;XV.UserAccountList&lt;/strong&gt; don’t have names, so Enyo automatically names them (apparently) based on the &lt;a href=&quot;http://enyojs.com/docs/2.4.0/key-concepts/kinds.html&quot;&gt;kind name&lt;/a&gt;, for example something of the kind &lt;em&gt;ListItem&lt;/em&gt; is named &lt;em&gt;listItem&lt;/em&gt;. I found this at random after a lot of trial and error and it’s not a bullet proof solution. Can be bettered.&lt;/p&gt;

&lt;p&gt;It’s strange because if you encapsulate that code with&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot; data-lang=&quot;js&quot;&gt;&lt;span class=&quot;nx&quot;&gt;XT&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;extensions&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;sip_account&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;initList&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
 &lt;span class=&quot;c1&quot;&gt;//Code here&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;as is done with &lt;strong&gt;parameter.js&lt;/strong&gt; and &lt;strong&gt;workspace.js&lt;/strong&gt; (and in the xTuple tutorial you are supposed to do that with a new business object), it doesn’t work. I have no idea why. This might be ‘wrong’ or against xTuple coding norms; I will find out and update this post ASAP. But it &lt;em&gt;does&lt;/em&gt; work this way. * shrugs *&lt;/p&gt;

&lt;p&gt;That said, as we’ve created the &lt;strong&gt;list.js&lt;/strong&gt; file, we need to ad it to our package by editing &lt;strong&gt;/path/to/xtuple-extensions/source/sip_account/client/views/package.js&lt;/strong&gt;:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-js&quot; data-lang=&quot;js&quot;&gt;&lt;span class=&quot;nx&quot;&gt;enyo&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;depends&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
&lt;span class=&quot;s2&quot;&gt;&quot;list.js&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
&lt;span class=&quot;s2&quot;&gt;&quot;workspace.js&quot;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;That’s all. Rebuild the app and restart your server and when you select Setup &amp;gt; User Accounts in the web app you should see the Sip URI displayed on the User Accounts that have the Sip Account data. Add a new User Account to try this out.&lt;/p&gt;

</description>
        <pubDate>Fri, 25 Jul 2014 15:06:52 +0000</pubDate>
        <link>http://julianalouback.com/tech/2014/07/25/kickstarting-the-jscommunicator-xtuple-extension/</link>
        <guid isPermaLink="true">http://julianalouback.com/tech/2014/07/25/kickstarting-the-jscommunicator-xtuple-extension/</guid>
        
        
        <category>tech</category>
        
      </item>
    
      <item>
        <title>Contribute a JSCommunicator Translation</title>
        <description>&lt;p&gt;To those who don’t know, JSCommunicator is a SIP communication tool developed in HTML and JavaScript, currently supporting audio/video calls and SIP SIMPLE instant messaging. We’ve recently added i18n internationalization support for English, Portuguese, Spanish, French and now Hebrew. We would like to have &lt;a href=&quot;https://github.com/opentelecoms-org/jscommunicator/issues/5&quot;&gt;support for other languages&lt;/a&gt; and it’s a pretty straightforward process to add a translation, so even if you do not have a tech background you can contribute!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Setup&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;First, &lt;a href=&quot;https://github.com/&quot;&gt;create and account in Github&lt;/a&gt;. Github is a hosting service for git repositories and open source projects get to use Github for FREE! As a result, many open source project repositories are hosted on Github, including but not limited to &lt;a href=&quot;https://github.com/opentelecoms-org/jscommunicator&quot;&gt;JSCommunicator&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Once you have created your git account, follow steps 1-4 in the section Setting up git listed in &lt;a href=&quot;https://help.github.com/articles/set-up-git&quot;&gt;Github’s setup tutorial&lt;/a&gt;. This will help you install git and configure anything you need.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fork&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Now fork the JSCommunicator repo. I really like the word ‘fork’. Great term. If this is your first fork, know that this will make a copy of the JSCommunicator repo and all its respective code to your Github account. Here’s how we go about it:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;
    &lt;p&gt;Sign in to &lt;a href=&quot;http://github.com&quot;&gt;Github&lt;/a&gt;&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Browse to https://github.com/opentelecoms-org/jscommunicator.git&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;Somewhere around the upper left corner, you will see a button labeled ‘Fork’. Click that.&lt;/p&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;img src=&quot;/assets/fork.jpg&quot; alt=&quot;fork&quot; /&gt;&lt;/p&gt;

&lt;p&gt;You should now have your own JSCommunicator repository on your Github account. On your main page select the tab ‘Repositories’. It should be the first on the list. The url will be https://github.com/YOUR-USERNAME/jscommunicator. My Github username is JLouback, so my jscommunicator repo can be found at https://github.com/JLouback/jscommunicator.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Clone&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Now you should download all that code to your local machine so you can edit it. On your JSCommunicator page somewhere around the lower right corner you’ll see a field entitled HTTPS clone URL:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/cloneURL.jpg&quot; alt=&quot;clone&quot; /&gt;&lt;/p&gt;

&lt;p&gt;I don’t know if this is correct, but you can just copy the URL on your browser too. I do that.&lt;/p&gt;

&lt;p&gt;Now on your terminal, navigate to the directory you’d like to place your repository and enter&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;n&quot;&gt;git&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;clone&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;https&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:/&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;github&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;com&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;YOUR&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;USERNAME&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;jscommunicator&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;The current directory should now have a folder named ‘jscommunicator’ and in it is all the JSCommunicator code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Switch branches&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The JSCommunicator repo has a branch for the i18n support. A branch is basically a copy of the code that you can modify without changing the original code. It’s great for adding new features, once everything is done and tested you can add the new feature to the original code. A more detailed explanation of branches can be found &lt;a href=&quot;https://help.github.com/articles/creating-and-deleting-branches-within-your-repository&quot;&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;On the terminal, enter&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;n&quot;&gt;cd&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;jscommunicator&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;git&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;branch&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;a&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;This will list all the branches in the repository, both local and remote. There should be a branch named ‘remotes/origin/i18n-support’. Switch to this branch by entering&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;n&quot;&gt;git&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;checkout&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;--&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;track&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;origin&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i18n&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;support&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;This will create a local branch named i18n-support with all the contents of the remote i18n-branch in https://github.com/opentelecoms-org/jscommunicator.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Create a .properties file&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Now your jscommunicator directory will have some new files for the internationalization functionality. Among them you should find an INTERNATIONALIZATION_README file with instructions for adding a JSCommunicator translation. It’s a less detailed version of this post, but it’s worth a read in case I’ve missed something here.&lt;/p&gt;

&lt;p&gt;Go to the directory ‘internationalization’. You’ll see a few .properties files with different language codes. Choose the language of your preference for the translation base and make a copy of it in the internationalization directory. Your copy should be named ‘Messages_LANGUAGECODE.properties’. For example, the language code for german is ‘de’, so the german file should be named ‘Messages_de.properties’. If you’re not sure about the code for your language, please check out this &lt;a href=&quot;http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes&quot;&gt;list of ISO 639-1 language codes&lt;/a&gt; so you’re using the same code as (most) browsers.&lt;/p&gt;

&lt;p&gt;The .properties file is list of key-value pairs, a word or a few words joined by ‘_’ which is the key, the equals sign ‘=’ then a word or phrase which is the value. Do &lt;em&gt;not&lt;/em&gt; change anything to the &lt;em&gt;left&lt;/em&gt; of the equals sign. Translate what is on the &lt;em&gt;right&lt;/em&gt; of the equals sign.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Modify available_languages.xml&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Go back to the root directory (jscommunicator) and open the file named ‘available_languages.ruby’. This .ruby has a series of language elements. At the end of the last ‘&amp;lt;/language&amp;gt;’ add a new language element with your language display name and code, copying the previous languge elements. You actually can put your language element anwhere, as long as it’s after the ‘&lt;list&gt;' and before the &lt;/list&gt;, as well as not cutting into any of the other language elements. Your display name should be the name of the language in its native language, for example for a french translation we’ll put ‘Français’ (I think). And the code is the code we used for the .properties file. Here’s how it should look:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;language&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;display&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Deutsch&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;/display&amp;gt;
    &amp;lt;code&amp;gt;de&amp;lt;/&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;code&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;/language&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Save your changes and voila!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Test your work&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Once you’ve made a .properties file, JSCommunicator will automatically load your translation if you’ve set your browser preference to that language. If your browser preference is french, it will load the Messages_fr.properties. If your browser preference is a language we don’t have a translation for (let’s say german), JScommunicator will load the default Messages.properties file which is in english.&lt;/p&gt;

&lt;p&gt;The available_languages.ruby is used to build a language selection menu. If you add a language element without a corresponding .properties file, JSCommunicator will throw a JS error and load the default (english) translation. The same happens if you use different language codes in the .properties file name and the available_languages.ruby. The error won’t disturb your use of JSCommunicator, it just won’t load the language you selected. No harm, no foul. But if you want others to use your translation, do take care to do this correctly.&lt;/p&gt;

&lt;p&gt;If you read the INTERNATIONALIZATION_README, you will have seen that we need the jquery.i18n.properties.js file that’s included in the .html pages. You can download that code &lt;a href=&quot;https://code.google.com/p/jquery-i18n-properties/&quot;&gt;here&lt;/a&gt;. Be sure to place it in the jscommunicator directory. This is only necessary if you want to see your addition at work, you don’t need this file to contribute your translation. There are other 3rd party code dependencies to run JSCommunicator too as you may have noticed. Just creating the .properties file and adding a language element to available_languages.ruby, if done correctly, is enough to make a pull request.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Commit&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here’s the part where we load all your local changes to your remote repository. In the terminal, navigate to the root directory (jscommunicator) and enter&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;n&quot;&gt;git&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;add&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;internationalization&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;/&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Messages_de&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;properties&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;git&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;add&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;available_languages&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;ruby&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;Of course, please replace ‘Messages_de.properties’ with the name of your new .properties file. And mind you, we don’t have a german translation yet!&lt;/p&gt;

&lt;p&gt;Then enter&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;n&quot;&gt;git&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;commit&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;m&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;German translation&quot;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;You can enter whatever you’d like in the “ “ part, it’s a good idea to explain what language you are adding. Now we can push the changes:&lt;/p&gt;

&lt;figure class=&quot;highlight&quot;&gt;&lt;pre&gt;&lt;code class=&quot;language-ruby&quot; data-lang=&quot;ruby&quot;&gt;&lt;span class=&quot;n&quot;&gt;git&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;push&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;u&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;origin&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i18n&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;support&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;

&lt;p&gt;You should now see the changes you made in your Github account page at github.com/YOUR_USER/jscommunicator. The message you put in double quotations (“ “) in the commit will be beside each modified file. This push also creates a new branch in your jscommunicator repository, now you will have a ‘master’ and a ‘i18n-support’ branch.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pull&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Now it’s time to share your translation with everyone else if you feel so inclined. Your version of JSCommunicator has a new translation but not the official version. First navigate to you jscommunicator repo at github.com/YOUR_USER/jscommunicator and make sure you are on the i18n-branch as that will contain your changes.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/branches.jpg&quot; alt=&quot;branches&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Click the green button directly to the left of the branch drop down menu shown above. This will create a pull request to add your new code to the official jscommunicator code. Once you click that button, you should see this:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/pull.jpg&quot; alt=&quot;pull&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Make sure that the base repo (the first on the line above the green Create pull request button) is opentelecoms-org:&lt;strong&gt;i18n-support&lt;/strong&gt; and not opentelecoms-org:&lt;strong&gt;master&lt;/strong&gt; or another branch name. If it is, just click ‘Edit’ on the right and you’ll be able to select the branch from a dropdown like so:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/pullEdit.jpg&quot; alt=&quot;pullEdit&quot; /&gt;&lt;/p&gt;

&lt;p&gt;If first repo is opentelecoms-org:i18n-support and the second is YOUR_USER:i18n-support and you’ve verified (scroll down) that there are 2 files changed which are your new .properties file and the added element to the available_languages.xml, go ahead and press create pull request. It’ll open a window with a text box you can add an explanation to with one final ‘Create pull request’ button. Click that and you’re done! You’ve kindly contributed with a translation for JSCommunicator!&lt;/p&gt;
</description>
        <pubDate>Thu, 17 Jul 2014 08:06:52 +0000</pubDate>
        <link>http://julianalouback.com/tutorial/2014/07/17/contribute-a-jscommunicator-translation/</link>
        <guid isPermaLink="true">http://julianalouback.com/tutorial/2014/07/17/contribute-a-jscommunicator-translation/</guid>
        
        
        <category>tutorial</category>
        
      </item>
    
  </channel>
</rss>
